Merge remote-tracking branch 'upstream/development' into Atom/santorac/RemixableMaterialTypes4_Layering

Signed-off-by: santorac <55155825+santorac@users.noreply.github.com>
This commit is contained in:
santorac
2022-02-11 09:37:44 -08:00
90 changed files with 1890 additions and 513 deletions
@@ -61,6 +61,7 @@ class TestAutomation(TestAutomationBase):
from . import Graph_HappyPath_ZoomInZoomOut as test_module
self._run_test(request, workspace, editor, test_module)
@pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.")
def test_NodePalette_HappyPath_CanSelectNode(self, request, workspace, editor, launcher_platform):
from . import NodePalette_HappyPath_CanSelectNode as test_module
self._run_test(request, workspace, editor, test_module)
@@ -113,6 +114,7 @@ class TestAutomation(TestAutomationBase):
from . import Debugger_HappyPath_TargetMultipleGraphs as test_module
self._run_test(request, workspace, editor, test_module)
@pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.")
@pytest.mark.parametrize("level", ["tmp_level"])
def test_Debugger_HappyPath_TargetMultipleEntities(self, request, workspace, editor, launcher_platform, project, level):
def teardown():
@@ -174,6 +176,7 @@ class TestAutomation(TestAutomationBase):
from . import ScriptEvents_ReturnSetType_Successfully as test_module
self._run_test(request, workspace, editor, test_module)
@pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.")
def test_NodeCategory_ExpandOnClick(self, request, workspace, editor, launcher_platform):
from . import NodeCategory_ExpandOnClick as test_module
self._run_test(request, workspace, editor, test_module)
@@ -187,6 +190,7 @@ class TestAutomation(TestAutomationBase):
from . import VariableManager_UnpinVariableType_Works as test_module
self._run_test(request, workspace, editor, test_module)
@pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.")
def test_Node_HappyPath_DuplicateNode(self, request, workspace, editor, launcher_platform):
from . import Node_HappyPath_DuplicateNode as test_module
self._run_test(request, workspace, editor, test_module)
@@ -263,6 +267,7 @@ class TestScriptCanvasTests(object):
timeout=60,
)
@pytest.mark.xfail(reason="Test fails on nightly build builds, it needs to be fixed.")
def test_VariableManager_Default_CreateDeleteVars(self, request, editor, launcher_platform):
var_types = ["Boolean", "Color", "EntityID", "Number", "String", "Transform", "Vector2", "Vector3", "Vector4"]
expected_lines = [f"Success: {var_type} variable is created" for var_type in var_types]
@@ -37,6 +37,7 @@ namespace AzFramework::SurfaceData
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AZStd::fixed_vector<SurfaceTagWeight, Constants::MaxSurfaceWeights>>();
serializeContext->Class<SurfacePoint>()
->Field("m_position", &SurfacePoint::m_position)
->Field("m_normal", &SurfacePoint::m_normal)
@@ -46,6 +47,7 @@ namespace AzFramework::SurfaceData
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<AZStd::fixed_vector<SurfaceTagWeight, Constants::MaxSurfaceWeights>>();
behaviorContext->Class<SurfacePoint>("AzFramework::SurfaceData::SurfacePoint")
->Attribute(AZ::Script::Attributes::Category, "SurfaceData")
->Constructor()
@@ -10,13 +10,19 @@
#include <AzCore/Math/Crc.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/containers/fixed_vector.h>
namespace AzFramework::SurfaceData
{
namespace Constants
{
static constexpr const char* s_unassignedTagName = "(unassigned)";
//! The maximum number of surface weights that we can store.
//! For performance reasons, we want to limit this so that we can preallocate the max size in advance.
//! The current number is chosen to be higher than expected needs, but small enough to avoid being excessively wasteful.
//! (Dynamic structures would end up taking more memory than what we're preallocating)
static constexpr size_t MaxSurfaceWeights = 16;
}
struct SurfaceTagWeight
@@ -70,7 +76,7 @@ namespace AzFramework::SurfaceData
}
};
using SurfaceTagWeightList = AZStd::vector<SurfaceTagWeight>;
using SurfaceTagWeightList = AZStd::fixed_vector<SurfaceTagWeight, Constants::MaxSurfaceWeights>;
struct SurfacePoint final
{
@@ -7,6 +7,7 @@
*/
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
#include <AzCore/Interface/Interface.h>
@@ -117,9 +118,16 @@ namespace AzToolsFramework
{
}
bool EditorEntityUiHandlerBase::OnEntityDoubleClick([[maybe_unused]] AZ::EntityId entityId) const
bool EditorEntityUiHandlerBase::OnOutlinerItemDoubleClick([[maybe_unused]] const QModelIndex& index) const
{
return false;
}
AZ::EntityId EditorEntityUiHandlerBase::GetEntityIdFromIndex(const QModelIndex& index)
{
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
return AZ::EntityId(firstColumnIndex.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
}
} // namespace AzToolsFramework
@@ -21,7 +21,6 @@ class QTreeView;
namespace AzToolsFramework
{
//! Defines a handler that can customize entity UI appearance and behavior in the Entity Outliner.
//! This class is meant to be abstract, entities do not have a handler by default.
class EditorEntityUiHandlerBase
{
protected:
@@ -33,7 +32,7 @@ namespace AzToolsFramework
public:
EditorEntityUiHandlerId GetHandlerId();
// # Entity Outliner
// # Entity Outliner Item
//! Returns the item info string that is appended to the item name in the Outliner.
virtual QString GenerateItemInfoString(AZ::EntityId entityId) const;
@@ -41,10 +40,12 @@ namespace AzToolsFramework
virtual QString GenerateItemTooltip(AZ::EntityId entityId) const;
//! Returns the item icon pixmap to display in the Outliner.
virtual QIcon GenerateItemIcon(AZ::EntityId entityId) const;
//! Returns whether the element's lock and visibility state should be accessible in the Outliner
virtual bool CanToggleLockVisibility(AZ::EntityId entityId) const;
//! Returns whether the element's name should be editable
virtual bool CanRename(AZ::EntityId entityId) const;
//! Returns whether the element's lock and visibility state should be accessible in the Outliner
virtual bool CanToggleLockVisibility(AZ::EntityId entityId) const;
// Qt-specific painting functions
//! Paints the background of the item in the Outliner.
virtual void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
@@ -54,24 +55,27 @@ namespace AzToolsFramework
//! Paints the background of the descendant branches of the item in the Outliner.
virtual void PaintDescendantBranchBackground(QPainter* painter, const QTreeView* view, const QRect& rect,
const QModelIndex& index, const QModelIndex& descendantIndex) const;
//! Paints visual elements on the foreground of the item in the Outliner.
virtual void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
//! Paints visual elements on the foreground of the descendants of the item in the Outliner.
virtual void PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
const QModelIndex& descendantIndex) const;
// Outliner-specific interactions
//! Triggered when the entity is clicked in the Outliner.
//! @return True if the click has been handled and should not be propagated, false otherwise.
virtual bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const;
//! Triggered when the entity is double-clicked in the Outliner.
//! @return True if the double-click has been handled and should not be propagated, false otherwise.
virtual bool OnOutlinerItemDoubleClick(const QModelIndex& index) const;
//! Triggered when an entity's children are expanded in the Outliner.
virtual void OnOutlinerItemExpand(const QModelIndex& index) const;
//! Triggered when an entity's children are collapsed in the Outliner.
virtual void OnOutlinerItemCollapse(const QModelIndex& index) const;
//! Triggered when the entity is double clicked in the Outliner or in the Viewport.
//! @return True if the double click has been handled and should not be propagated, false otherwise.
virtual bool OnEntityDoubleClick(AZ::EntityId entityId) const;
protected:
static AZ::EntityId GetEntityIdFromIndex(const QModelIndex& index);
private:
EditorEntityUiHandlerId m_handlerId = 0;
@@ -945,7 +945,7 @@ namespace AzToolsFramework
{
if (AZ::EntityId entityId = GetEntityIdFromIndex(index); auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId))
{
entityUiHandler->OnEntityDoubleClick(entityId);
entityUiHandler->OnOutlinerItemDoubleClick(index);
}
}
@@ -33,7 +33,7 @@ namespace AzToolsFramework
}
}
QIcon LevelRootUiHandler::GenerateItemIcon(AZ::EntityId /*entityId*/) const
QIcon LevelRootUiHandler::GenerateItemIcon([[maybe_unused]] AZ::EntityId entityId) const
{
return QIcon(m_levelRootIconPath);
}
@@ -62,17 +62,18 @@ namespace AzToolsFramework
return infoString;
}
bool LevelRootUiHandler::CanToggleLockVisibility(AZ::EntityId /*entityId*/) const
bool LevelRootUiHandler::CanToggleLockVisibility([[maybe_unused]] AZ::EntityId entityId) const
{
return false;
}
bool LevelRootUiHandler::CanRename(AZ::EntityId /*entityId*/) const
bool LevelRootUiHandler::CanRename([[maybe_unused]] AZ::EntityId entityId) const
{
return false;
}
void LevelRootUiHandler::PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const
void LevelRootUiHandler::PaintItemBackground(
QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const
{
if (!painter)
{
@@ -94,8 +95,10 @@ namespace AzToolsFramework
painter->restore();
}
bool LevelRootUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
bool LevelRootUiHandler::OnOutlinerItemDoubleClick(const QModelIndex& index) const
{
AZ::EntityId entityId = GetEntityIdFromIndex(index);
if (auto prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get();
!prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
@@ -33,7 +33,7 @@ namespace AzToolsFramework
bool CanToggleLockVisibility(AZ::EntityId entityId) const override;
bool CanRename(AZ::EntityId entityId) const override;
void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
bool OnOutlinerItemDoubleClick(const QModelIndex& index) const override;
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -99,7 +99,7 @@ namespace AzToolsFramework
return;
}
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const bool isFirstColumn = index.column() == EntityOutlinerListModel::ColumnName;
const bool isLastColumn = index.column() == EntityOutlinerListModel::ColumnLockToggle;
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
@@ -183,7 +183,7 @@ namespace AzToolsFramework
return;
}
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const QTreeView* outlinerTreeView(qobject_cast<const QTreeView*>(option.widget));
const int ancestorLeft = outlinerTreeView->visualRect(index).left() + (m_prefabBorderThickness / 2) - 1;
@@ -283,7 +283,7 @@ namespace AzToolsFramework
void PrefabUiHandler::PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const QPoint offset = QPoint(-18, 3);
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
const int iconSize = 16;
@@ -385,7 +385,7 @@ namespace AzToolsFramework
bool PrefabUiHandler::OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const QPoint offset = QPoint(-18, 3);
if (m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId))
@@ -411,7 +411,7 @@ namespace AzToolsFramework
void PrefabUiHandler::OnOutlinerItemCollapse(const QModelIndex& index) const
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
@@ -420,8 +420,10 @@ namespace AzToolsFramework
}
}
bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
bool PrefabUiHandler::OnOutlinerItemDoubleClick(const QModelIndex& index) const
{
AZ::EntityId entityId = GetEntityIdFromIndex(index);
if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
// Focus on this prefab
@@ -43,8 +43,8 @@ namespace AzToolsFramework
const QModelIndex& index,
const QModelIndex& descendantIndex) const override;
bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
bool OnOutlinerItemDoubleClick(const QModelIndex& index) const override;
void OnOutlinerItemCollapse(const QModelIndex& index) const override;
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
protected:
Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
@@ -102,7 +102,7 @@ namespace AzToolsFramework
m_editButton->setAutoRaise(true);
m_editButton->setIcon(QIcon(":/stylesheet/img/UI20/open-in-internal-app.svg"));
m_editButton->setToolTip("Edit asset");
m_editButton->setVisible(false);
SetEditButtonVisible(false);
connect(m_editButton, &QToolButton::clicked, this, &PropertyAssetCtrl::OnEditButtonClicked);
@@ -961,12 +961,16 @@ namespace AzToolsFramework
AzFramework::StringFunc::Path::GetFileName(assetPath.c_str(), m_defaultAssetHint);
}
m_browseEdit->setPlaceholderText((m_defaultAssetHint + m_DefaultSuffix).c_str());
UpdateEditButton();
}
void PropertyAssetCtrl::UpdateAssetDisplay()
{
UpdateThumbnail();
UpdateEditButton();
if (m_currentAssetType == AZ::Data::s_invalidAssetType)
{
return;
@@ -1109,7 +1113,9 @@ namespace AzToolsFramework
void PropertyAssetCtrl::SetEditButtonVisible(bool visible)
{
m_editButton->setVisible(visible);
m_showEditButton = visible;
m_editButton->setVisible(m_showEditButton);
UpdateEditButton();
}
void PropertyAssetCtrl::SetEditButtonIcon(const QIcon& icon)
@@ -1205,6 +1211,15 @@ namespace AzToolsFramework
m_thumbnail->ClearThumbnail();
}
void PropertyAssetCtrl::UpdateEditButton()
{
// if Edit button is in use (shown), enable/disable it depending on the current asset id.
if (m_showEditButton && m_disableEditButtonWhenNoAssetSelected)
{
m_editButton->setEnabled(GetCurrentAssetID().IsValid());
}
}
void PropertyAssetCtrl::SetClearButtonEnabled(bool enable)
{
m_browseEdit->setClearButtonEnabled(enable);
@@ -1236,6 +1251,17 @@ namespace AzToolsFramework
return m_hideProductFilesInAssetPicker;
}
void PropertyAssetCtrl::SetDisableEditButtonWhenNoAssetSelected(bool disableEditButtonWhenNoAssetSelected)
{
m_disableEditButtonWhenNoAssetSelected = disableEditButtonWhenNoAssetSelected;
UpdateEditButton();
}
bool PropertyAssetCtrl::GetDisableEditButtonWhenNoAssetSelected() const
{
return m_disableEditButtonWhenNoAssetSelected;
}
void PropertyAssetCtrl::SetShowThumbnail(bool enable)
{
m_showThumbnail = enable;
@@ -1349,6 +1375,12 @@ namespace AzToolsFramework
GUI->SetEditButtonTooltip(tr(buttonTooltip.c_str()));
}
}
else if (attrib == AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"))
{
bool disableEditButtonWhenNoAssetSelected = false;
attrValue->Read<bool>(disableEditButtonWhenNoAssetSelected);
GUI->SetDisableEditButtonWhenNoAssetSelected(disableEditButtonWhenNoAssetSelected);
}
else if (attrib == AZ::Edit::Attributes::DefaultAsset)
{
AZ::Data::AssetId assetId;
@@ -1597,6 +1629,12 @@ namespace AzToolsFramework
GUI->SetEditButtonTooltip(tr(buttonTooltip.c_str()));
}
}
else if (attrib == AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"))
{
bool disableEditButtonWhenNoAssetSelected = false;
attrValue->Read<bool>(disableEditButtonWhenNoAssetSelected);
GUI->SetDisableEditButtonWhenNoAssetSelected(disableEditButtonWhenNoAssetSelected);
}
}
void SimpleAssetPropertyHandlerDefault::WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node)
@@ -159,6 +159,10 @@ namespace AzToolsFramework
//! By default the asset picker shows both on an AZ::Asset<> property. You can hide product assets with this flag.
bool m_hideProductFilesInAssetPicker = false;
//! True to disable the edit button when there is no asset currently selected.
bool m_disableEditButtonWhenNoAssetSelected = false;
bool m_showEditButton = false;
bool m_showThumbnail = false;
bool m_showThumbnailDropDownButton = false;
EditCallbackType* m_thumbnailCallback = nullptr;
@@ -220,6 +224,9 @@ namespace AzToolsFramework
void SetHideProductFilesInAssetPicker(bool hide);
bool GetHideProductFilesInAssetPicker() const;
void SetDisableEditButtonWhenNoAssetSelected(bool disableEditButtonWhenNoAssetSelected);
bool GetDisableEditButtonWhenNoAssetSelected() const;
// Enable and configure a thumbnail widget that displays an asset preview and dropdown arrow for a dropdown menu
void SetShowThumbnail(bool enable);
bool GetShowThumbnail() const;
@@ -250,6 +257,7 @@ namespace AzToolsFramework
private:
void UpdateThumbnail();
void UpdateEditButton();
};
class AssetPropertyHandlerDefault
@@ -154,6 +154,16 @@ namespace AzToolsFramework
(void)debugName;
}
// provides an option to specify reading parent element attributes.
// This allows parent elements to override attributes of their children if needed.
virtual void ConsumeParentAttribute(WidgetType* widget, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName)
{
(void)widget;
(void)attrib;
(void)attrValue;
(void)debugName;
}
// override GetFirstInTabOrder, GetLastInTabOrder in your base class to define which widget gets focus first when pressing tab,
// and also what widget is last.
// for example, if your widget is a compound widget and contains, say, 5 buttons
@@ -40,7 +40,14 @@ namespace AzToolsFramework
}
void* classInstance = parent->FirstInstance(); // pointer to the owner class so we can read member variables and functions
auto consumeAttributes = [&](const auto& attributes, const char* name)
void* parentClassInstance = nullptr;
if (InstanceDataNode* parentInstanceDataNode = parent->GetParent())
{
parentClassInstance = parentInstanceDataNode->FirstInstance();
}
auto consumeAttributes = [this, classInstance, wid](const auto& attributes, const char* name)
{
for (size_t i = 0; i < attributes.size(); ++i)
{
@@ -50,25 +57,43 @@ namespace AzToolsFramework
}
};
auto consumeParentAttributes = [this, parentClassInstance, wid](const auto& attributes, const char* name)
{
if (parentClassInstance)
{
for (size_t i = 0; i < attributes.size(); ++i)
{
const auto& attrPair = attributes[i];
PropertyAttributeReader reader(parentClassInstance, &*attrPair.second);
ConsumeParentAttribute(wid, attrPair.first, &reader, name);
}
}
};
const AZ::SerializeContext::ClassElement* element = dataNode->GetElementMetadata();
if (element)
{
consumeAttributes(element->m_attributes, element->m_name);
const AZ::Edit::ElementData* elementEdit = dataNode->GetElementEditMetadata();
if (elementEdit)
if (const AZ::Edit::ElementData* elementEdit = dataNode->GetElementEditMetadata();
elementEdit != nullptr)
{
consumeAttributes(elementEdit->m_attributes, elementEdit->m_name);
}
}
if (dataNode->GetClassMetadata())
{
const AZ::Edit::ClassData* classEditData = dataNode->GetClassMetadata()->m_editData;
if (classEditData)
const AZ::SerializeContext::ClassElement* parentElement = parent != dataNode ?
dataNode->GetElementMetadata() :
nullptr;
if (parentElement != nullptr)
{
for (auto it = classEditData->m_elements.begin(); it != classEditData->m_elements.end(); ++it)
// Reuse the current instance element name for the debug name
consumeParentAttributes(parentElement->m_attributes, element->m_name);
if (const AZ::Edit::ElementData* elementEdit = parent->GetElementEditMetadata();
elementEdit != nullptr)
{
consumeAttributes(it->m_attributes, it->m_name);
consumeParentAttributes(elementEdit->m_attributes, elementEdit->m_name);
}
}
}
@@ -66,6 +66,12 @@ namespace AzToolsFramework
class GenericEnumPropertyComboBoxHandler
: public GenericComboBoxHandler<ValueType>
{
virtual void ConsumeParentAttribute(GenericComboBoxCtrlBase* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override
{
// Simply re-route to ConsumeAttribute since no special logic is needed.
ConsumeAttribute(GUI, attrib, attrValue, debugName);
}
virtual void ConsumeAttribute(GenericComboBoxCtrlBase* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override
{
(void)debugName;
@@ -15,7 +15,7 @@ namespace UnitTest
// When no containers are in the way, the function will just return the entityId of the entity that was clicked.
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -29,7 +29,7 @@ namespace UnitTest
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]); // Containers are closed by default
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -47,7 +47,7 @@ namespace UnitTest
m_containerEntityInterface->SetContainerOpen(m_entityMap[StreetEntityName], true);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -65,7 +65,7 @@ namespace UnitTest
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CityEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -85,7 +85,7 @@ namespace UnitTest
m_containerEntityInterface->SetContainerOpen(m_entityMap[CityEntityName], true);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -8,6 +8,7 @@
#include <Tests/FocusMode/EditorFocusModeFixture.h>
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <Tests/BoundsTestComponent.h>
@@ -93,10 +94,13 @@ namespace UnitTest
entity->CreateComponent<UnitTest::BoundsTestComponent>();
entity->Activate();
// Move the CarEntity so it's out of the way.
AZ::TransformBus::Event(m_entityMap[CarEntityName], &AZ::TransformBus::Events::SetWorldTranslation, WorldCarEntityPosition);
// Move the City so that it is in view
AZ::TransformBus::Event(m_entityMap[CityEntityName], &AZ::TransformBus::Events::SetWorldTranslation, s_worldCityEntityPosition);
// Setup the camera so the Car entity is in view.
// Move the CarEntity so that it's not overlapping with the rest
AZ::TransformBus::Event(m_entityMap[CarEntityName], &AZ::TransformBus::Events::SetWorldTranslation, s_worldCarEntityPosition);
// Setup the camera so the entities is in view.
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromQuaternionAndTranslation(
@@ -113,4 +117,5 @@ namespace UnitTest
return entity->GetId();
}
} // namespace UnitTest
@@ -8,7 +8,6 @@
#pragma once
#include <AzCore/Component/TransformBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
@@ -38,9 +37,6 @@ namespace UnitTest
AzToolsFramework::EntityIdList GetSelectedEntities();
AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
AzFramework::CameraState m_cameraState;
inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f);
inline static const char* CityEntityName = "City";
inline static const char* StreetEntityName = "Street";
@@ -49,7 +45,11 @@ namespace UnitTest
inline static const char* Passenger1EntityName = "Passenger1";
inline static const char* Passenger2EntityName = "Passenger2";
inline static AZ::Vector3 WorldCarEntityPosition = AZ::Vector3(5.0f, 15.0f, 0.0f);
AzFramework::CameraState m_cameraState;
inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f);
inline static AZ::Vector3 s_worldCityEntityPosition = AZ::Vector3(5.0f, 10.0f, 0.0f);
inline static AZ::Vector3 s_worldCarEntityPosition = AZ::Vector3(5.0f, 15.0f, 0.0f);
};
} // namespace UnitTest
@@ -45,5 +45,20 @@ namespace UnitTest
// Click the entity in the viewport
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(carScreenPosition)->MouseLButtonDown()->MouseLButtonUp();
}
void BoxSelectOnViewport()
{
// Calculate the position in screen space of where to begin and end the box select action
const auto beginningPositionWorldBoxSelect = AzFramework::WorldToScreen(AZ::Vector3(-10.0f, 15.0f, 5.0f), m_cameraState);
const auto endingPositionWorldBoxSelect = AzFramework::WorldToScreen(AZ::Vector3(10.0f, 15.0f, -5.0f), m_cameraState);
// Perform a box select in the viewport
m_actionDispatcher->SetStickySelect(true)
->CameraState(m_cameraState)
->MousePosition(beginningPositionWorldBoxSelect)
->MouseLButtonDown()
->MousePosition(endingPositionWorldBoxSelect)
->MouseLButtonUp();
}
};
} // namespace UnitTest
@@ -13,7 +13,7 @@ namespace UnitTest
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnLevel)
{
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -27,7 +27,7 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -41,7 +41,7 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -55,7 +55,7 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[SportsCarEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -68,10 +68,71 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger1EntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_EQ(selectedEntitiesAfter.size(), 0);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnLevel)
{
// Do a box select that includes all entities in the fixture
BoxSelectOnViewport();
// Entities are selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_THAT(selectedEntitiesAfter,
UnorderedElementsAre(
m_entityMap[CityEntityName],
m_entityMap[StreetEntityName],
m_entityMap[CarEntityName],
m_entityMap[Passenger1EntityName],
m_entityMap[SportsCarEntityName],
m_entityMap[Passenger2EntityName]
)
);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnChild)
{
// Set the focus on the Passenger1 Entity (child of the entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]);
// Do a box select that includes all entities in the fixture
BoxSelectOnViewport();
// Entities are selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_THAT(selectedEntitiesAfter,
UnorderedElementsAre(
m_entityMap[StreetEntityName],
m_entityMap[CarEntityName],
m_entityMap[Passenger1EntityName],
m_entityMap[SportsCarEntityName],
m_entityMap[Passenger2EntityName]
)
);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnLeaf)
{
// Set the focus on the Passenger1 Entity (child of the entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger1EntityName]);
// Do a box select that includes all entities in the fixture
BoxSelectOnViewport();
// Entities are selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_THAT(selectedEntitiesAfter,
UnorderedElementsAre(
m_entityMap[Passenger1EntityName]
)
);
}
} // namespace UnitTest
@@ -82,7 +82,7 @@ namespace AZ
// Register Shader Asset Builder
AssetBuilderSDK::AssetBuilderDesc shaderAssetBuilderDescriptor;
shaderAssetBuilderDescriptor.m_name = "Shader Asset Builder";
shaderAssetBuilderDescriptor.m_version = 110; // Add "Definitions" field to shader asset to support convenient addition of preprocessor definitions
shaderAssetBuilderDescriptor.m_version = 111; // Enable shader PDB generation globally if Atom/GraphicsDevMode settings registry key is set
shaderAssetBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern( AZStd::string::format("*.%s", RPI::ShaderSourceData::Extension), AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
shaderAssetBuilderDescriptor.m_busId = azrtti_typeid<ShaderAssetBuilder>();
shaderAssetBuilderDescriptor.m_createJobFunction = AZStd::bind(&ShaderAssetBuilder::CreateJobs, &m_shaderAssetBuilder, AZStd::placeholders::_1, AZStd::placeholders::_2);
@@ -318,10 +318,8 @@ namespace AZ
RPI::RenderPipelineDescriptor renderPipelineDescriptor = *RPI::GetDataFromAnyAsset<RPI::RenderPipelineDescriptor>(pipelineAsset);
renderPipelineDescriptor.m_name = AZStd::string::format("%s_%i", renderPipelineDescriptor.m_name.c_str(), viewportContext->GetId());
// Make sure non-msaa super variant is used for non-msaa pipeline
bool isNonMsaaPipeline = (renderPipelineDescriptor.m_renderSettings.m_multisampleState.m_samples == 1);
const char* supervariantName = isNonMsaaPipeline ? AZ::RPI::NoMsaaSupervariantName : "";
AZ::RPI::ShaderSystemInterface::Get()->SetSupervariantName(AZ::Name(supervariantName));
// The default pipeline determines the initial MSAA state for the application
AZ::RPI::RPISystemInterface::Get()->SetApplicationMultisampleState(renderPipelineDescriptor.m_renderSettings.m_multisampleState);
if (!scene->GetRenderPipeline(AZ::Name(renderPipelineDescriptor.m_name)))
{
@@ -34,11 +34,11 @@
"Attachment": "Output"
}
},
"MultisampleSource": {
"Pass": "Pipeline"
},
"ImageDescriptor": {
"Format": "D32_FLOAT_S8X24_UINT",
"MultisampleState": {
"samples": 4
},
"SharedQueueMask": "Graphics"
}
}
@@ -29,6 +29,24 @@
"RootShaderVariantAssetFileName": "diffuseprobegridrender_null_0.azshadervariant"
}
]
},
{
"Name": "NoMSAA",
"RootShaderVariantAssets":
[
{
"APIName": "dx12",
"RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_dx12_0.azshadervariant"
},
{
"APIName": "vulkan",
"RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_vulkan_0.azshadervariant"
},
{
"APIName": "null",
"RootShaderVariantAssetFileName": "diffuseprobegridrender-nomsaa_null_0.azshadervariant"
}
]
}
]
}
@@ -92,21 +92,24 @@ namespace AZ
AZ_Error("DiffuseProbeGridFeatureProcessor", m_probeGridRenderData.m_srgLayout != nullptr, "Failed to find ObjectSrg layout");
}
// initialize the buffer pools for the DiffuseProbeGrid visualization
m_visualizationBufferPools = RHI::RayTracingBufferPools::CreateRHIRayTracingBufferPools();
m_visualizationBufferPools->Init(device);
// load probe visualization model, the BLAS will be created in OnAssetReady()
m_visualizationModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(
"Models/DiffuseProbeSphere.azmodel",
AZ::RPI::AssetUtils::TraceLevel::Assert);
if (!m_visualizationModelAsset.IsReady())
if (device->GetFeatures().m_rayTracing)
{
m_visualizationModelAsset.QueueLoad();
}
// initialize the buffer pools for the DiffuseProbeGrid visualization
m_visualizationBufferPools = RHI::RayTracingBufferPools::CreateRHIRayTracingBufferPools();
m_visualizationBufferPools->Init(device);
Data::AssetBus::MultiHandler::BusConnect(m_visualizationModelAsset.GetId());
// load probe visualization model, the BLAS will be created in OnAssetReady()
m_visualizationModelAsset = AZ::RPI::AssetUtils::GetAssetByProductPath<AZ::RPI::ModelAsset>(
"Models/DiffuseProbeSphere.azmodel",
AZ::RPI::AssetUtils::TraceLevel::Assert);
if (!m_visualizationModelAsset.IsReady())
{
m_visualizationModelAsset.QueueLoad();
}
Data::AssetBus::MultiHandler::BusConnect(m_visualizationModelAsset.GetId());
}
EnableSceneNotification();
}
@@ -115,10 +115,6 @@ namespace AZ
// all faces of the cubemap have been rendered, invoke the callback
m_callback(m_environmentCubeMapPass->GetTextureData(), m_environmentCubeMapPass->GetTextureFormat());
// remove the pipeline
m_scene->RemoveRenderPipeline(m_environmentCubeMapPipelineId);
m_environmentCubeMapPass = nullptr;
// restore exposures
sceneSrg->SetConstant(m_globalIblExposureConstantIndex, m_previousGlobalIblExposure);
sceneSrg->SetConstant(m_skyBoxExposureConstantIndex, m_previousSkyBoxExposure);
@@ -223,6 +219,16 @@ namespace AZ
}
}
void ReflectionProbe::OnRenderEnd()
{
if (m_environmentCubeMapPass && m_environmentCubeMapPass->IsFinished())
{
// remove the cubemap pipeline
// Note: this must be done here (not in Simulate) to avoid a race condition with other feature processors
m_scene->RemoveRenderPipeline(m_environmentCubeMapPipelineId);
m_environmentCubeMapPass = nullptr;
}
}
void ReflectionProbe::SetTransform(const AZ::Transform& transform)
{
@@ -282,7 +288,7 @@ namespace AZ
AZ::RPI::RenderPipelineDescriptor environmentCubeMapPipelineDesc;
environmentCubeMapPipelineDesc.m_mainViewTagName = "MainCamera";
environmentCubeMapPipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4;
environmentCubeMapPipelineDesc.m_renderSettings.m_multisampleState = RPI::RPISystemInterface::Get()->GetApplicationMultisampleState();
environmentCubeMapPipelineDesc.m_renderSettings.m_size.m_width = RPI::EnvironmentCubeMapPass::CubeMapFaceSize;
environmentCubeMapPipelineDesc.m_renderSettings.m_size.m_height = RPI::EnvironmentCubeMapPass::CubeMapFaceSize;
@@ -75,6 +75,7 @@ namespace AZ
void Init(RPI::Scene* scene, ReflectionRenderData* reflectionRenderData);
void Simulate(uint32_t probeIndex);
void OnRenderEnd();
const Vector3& GetPosition() const { return m_transform.GetTranslation(); }
const AZ::Transform& GetTransform() const { return m_transform; }
@@ -166,6 +166,18 @@ namespace AZ
}
}
void ReflectionProbeFeatureProcessor::OnRenderEnd()
{
// call OnRenderEnd on all reflection probes
for (uint32_t probeIndex = 0; probeIndex < m_reflectionProbes.size(); ++probeIndex)
{
AZStd::shared_ptr<ReflectionProbe>& reflectionProbe = m_reflectionProbes[probeIndex];
AZ_Assert(reflectionProbe.use_count() > 1, "ReflectionProbe found with no corresponding owner, ensure that RemoveProbe() is called before releasing probe handles");
reflectionProbe->OnRenderEnd();
}
}
ReflectionProbeHandle ReflectionProbeFeatureProcessor::AddProbe(const AZ::Transform& transform, bool useParallaxCorrection)
{
AZStd::shared_ptr<ReflectionProbe> reflectionProbe = AZStd::make_shared<ReflectionProbe>();
@@ -46,6 +46,7 @@ namespace AZ
void Activate() override;
void Deactivate() override;
void Simulate(const FeatureProcessor::SimulatePacket& packet) override;
void OnRenderEnd() override;
// find the reflection probe volumes that contain the position
using ReflectionProbeVector = AZStd::vector<AZStd::shared_ptr<ReflectionProbe>>;
@@ -42,6 +42,9 @@ namespace AZ
//! Returns if the current bakcend is a null renderer
bool IsNullRenderer();
//! Returns true if the Atom/GraphicsDevMode settings registry key is set
bool IsGraphicsDevModeEnabled();
}
}
@@ -9,9 +9,12 @@
#include <Atom/RHI/RHIUtils.h>
#include <Atom/RHI/RHISystemInterface.h>
#include <Atom/RHI/Factory.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/CommandLine/CommandLine.h>
static constexpr char GraphicsDevModeSetting[] = "/Atom/GraphicsDevMode";
namespace AZ
{
namespace RHI
@@ -134,5 +137,16 @@ namespace AZ
}
return false;
}
bool IsGraphicsDevModeEnabled()
{
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
bool graphicsDevMode = false;
if (settingsRegistry)
{
settingsRegistry->Get(graphicsDevMode, GraphicsDevModeSetting);
}
return graphicsDevMode;
}
}
}
@@ -12,10 +12,10 @@
#include <Atom/RHI.Edit/Utils.h>
#include <Atom/RHI.Reflect/DX12/PipelineLayoutDescriptor.h>
#include <Atom/RHI.Reflect/DX12/ShaderStageFunction.h>
#include <Atom/RHI/RHIUtils.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace AZ
@@ -253,9 +253,11 @@ namespace AZ
return false;
}
const bool graphicsDevMode = RHI::IsGraphicsDevModeEnabled();
// Compilation parameters
AZStd::string params = shaderCompilerArguments.MakeAdditionalDxcCommandLineString();
if (BuildHasDebugInfo(shaderCompilerArguments))
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
{
params += " -Zi"; // Generate debug information
params += " -Zss"; // Compute Shader Hash considering source information
@@ -284,7 +286,7 @@ namespace AZ
// If we use the auto-name (hash), there is no way we can retrieve that name apart from listing the directory.
// Instead, let's just generate that hash ourselves.
AZStd::string symbolDatabaseFileCliArgument{" "}; // when not debug: still insert a space between 5.dxil and 7.hlsl-in
if (BuildHasDebugInfo(shaderCompilerArguments))
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
{
// prepare .pdb filename:
AZStd::string md5hex = RHI::ByteToHexString(md5);
@@ -353,7 +355,7 @@ namespace AZ
byProducts.m_dynamicBranchCount = ByProducts::UnknownDynamicBranchCount;
}
if (BuildHasDebugInfo(shaderCompilerArguments))
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
{
byProducts.m_intermediatePaths.emplace(AZStd::move(objectCodeOutputFile));
}
@@ -11,6 +11,7 @@
#include <Atom/RHI.Reflect/Vulkan/Base.h>
#include <Atom/RHI.Reflect/Vulkan/ShaderStageFunction.h>
#include <Atom/RHI/RHIUtils.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
@@ -281,7 +282,9 @@ namespace AZ
args.m_destinationFolder = tempFolder.c_str();
const auto dxcInputFile = RHI::PrependFile(args); // Prepend header
if (BuildHasDebugInfo(shaderCompilerArguments))
const bool graphicsDevMode = RHI::IsGraphicsDevModeEnabled();
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
{
// dump intermediate "true final HLSL" file (shadername.vulkan.shadersource.prepend)
byProducts.m_intermediatePaths.insert(dxcInputFile);
@@ -334,7 +337,7 @@ namespace AZ
byProducts.m_dynamicBranchCount = ByProducts::UnknownDynamicBranchCount;
}
if (BuildHasDebugInfo(shaderCompilerArguments))
if (graphicsDevMode || BuildHasDebugInfo(shaderCompilerArguments))
{
byProducts.m_intermediatePaths.emplace(AZStd::move(objectCodeOutputFile));
}
@@ -86,6 +86,8 @@ namespace AZ
const RPISystemDescriptor& GetDescriptor() const override;
Name GetRenderApiName() const override;
uint64_t GetCurrentTick() const override;
void SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState) override;
const RHI::MultisampleState& GetApplicationMultisampleState() const override;
// AZ::Debug::TraceMessageBus::Handler overrides...
bool OnPreAssert(const char* fileName, int line, const char* func, const char* message) override;
@@ -136,6 +138,9 @@ namespace AZ
bool m_systemAssetsInitialized = false;
uint64_t m_renderTick = 0;
// Application multisample state
RHI::MultisampleState m_multisampleState;
};
} // namespace RPI
@@ -90,6 +90,10 @@ namespace AZ
//! Get the index of current render tick
virtual uint64_t GetCurrentTick() const = 0;
//! Application multisample state
virtual void SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState) = 0;
virtual const RHI::MultisampleState& GetApplicationMultisampleState() const = 0;
};
} // namespace RPI
@@ -60,6 +60,11 @@ namespace AZ
//! Same as above. Provided as a convenience when all arguments of the 'numthreads' attributes should be assigned to RHI::DispatchDirect::m_threadsPerGroup* variables.
AZ::Outcome<void, AZStd::string> GetComputeShaderNumThreads(const Data::Asset<ShaderAsset>& shaderAsset, RHI::DispatchDirect& dispatchDirect);
//! Get single image pixel value from raw image data
//! This assumes the imageData is not empty
template<typename T>
T GetImageDataPixelValue(AZStd::span<const uint8_t> imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex = 0);
//! Get single image pixel value for specified mip and slice
template<typename T>
T GetSubImagePixelValue(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex = 0, uint32_t mip = 0, uint32_t slice = 0);
@@ -434,5 +434,29 @@ namespace AZ
return m_renderTick;
}
void RPISystem::SetApplicationMultisampleState(const RHI::MultisampleState& multisampleState)
{
m_multisampleState = multisampleState;
bool isNonMsaaPipeline = (m_multisampleState.m_samples == 1);
const char* supervariantName = isNonMsaaPipeline ? AZ::RPI::NoMsaaSupervariantName : "";
AZ::RPI::ShaderSystemInterface::Get()->SetSupervariantName(AZ::Name(supervariantName));
// reinitialize pipelines for all scenes
for (auto& scene : m_scenes)
{
for (auto& renderPipeline : scene->GetRenderPipelines())
{
renderPipeline->GetRenderSettings().m_multisampleState = multisampleState;
renderPipeline->SetPassNeedsRecreate();
}
}
}
const RHI::MultisampleState& RPISystem::GetApplicationMultisampleState() const
{
return m_multisampleState;
}
} //namespace RPI
} //namespace AZ
@@ -232,16 +232,12 @@ namespace AZ
}
}
template<typename T>
T GetSubImagePixelValueInternal(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice)
size_t GetImageDataIndex(const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex)
{
AZStd::array values{ aznumeric_cast<T>(0) };
auto width = imageDescriptor.m_size.m_width;
const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format);
auto topLeft = AZStd::make_pair(x, y);
auto bottomRight = AZStd::make_pair(x + 1, y + 1);
GetSubImagePixelValues(imageAsset, topLeft, bottomRight, AZStd::span<T>(values), componentIndex, mip, slice);
return values[0];
return (y * width + x) * numComponents + componentIndex;
}
}
@@ -447,22 +443,60 @@ namespace AZ
return GetComputeShaderNumThreads(shaderAsset, &dispatchDirect.m_threadsPerGroupX, &dispatchDirect.m_threadsPerGroupY, &dispatchDirect.m_threadsPerGroupZ);
}
template<>
float GetImageDataPixelValue<float>(AZStd::span<const uint8_t> imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex)
{
size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex);
return Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
}
template<>
AZ::u32 GetImageDataPixelValue<AZ::u32>(AZStd::span<const uint8_t> imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex)
{
size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex);
return Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
}
template<>
AZ::s32 GetImageDataPixelValue<AZ::s32>(AZStd::span<const uint8_t> imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, uint32_t x, uint32_t y, uint32_t componentIndex)
{
size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex);
return Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
}
template<typename T>
T GetSubImagePixelValueInternal(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice)
{
if (!imageAsset.IsReady())
{
return aznumeric_cast<T>(0);
}
auto imageData = imageAsset->GetSubImageData(mip, slice);
if (imageData.empty())
{
return aznumeric_cast<T>(0);
}
return GetImageDataPixelValue<T>(imageData, imageAsset->GetImageDescriptor(), x, y, componentIndex);
}
template<>
float GetSubImagePixelValue<float>(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice)
{
return Internal::GetSubImagePixelValueInternal<float>(imageAsset, x, y, componentIndex, mip, slice);
return GetSubImagePixelValueInternal<float>(imageAsset, x, y, componentIndex, mip, slice);
}
template<>
AZ::u32 GetSubImagePixelValue<AZ::u32>(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice)
{
return Internal::GetSubImagePixelValueInternal<AZ::u32>(imageAsset, x, y, componentIndex, mip, slice);
return GetSubImagePixelValueInternal<AZ::u32>(imageAsset, x, y, componentIndex, mip, slice);
}
template<>
AZ::s32 GetSubImagePixelValue<AZ::s32>(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, uint32_t x, uint32_t y, uint32_t componentIndex, uint32_t mip, uint32_t slice)
{
return Internal::GetSubImagePixelValueInternal<AZ::s32>(imageAsset, x, y, componentIndex, mip, slice);
return GetSubImagePixelValueInternal<AZ::s32>(imageAsset, x, y, componentIndex, mip, slice);
}
bool GetSubImagePixelValues(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, AZStd::pair<uint32_t, uint32_t> topLeft, AZStd::pair<uint32_t, uint32_t> bottomRight, AZStd::span<float> outValues, uint32_t componentIndex, uint32_t mip, uint32_t slice)
@@ -478,16 +512,14 @@ namespace AZ
return false;
}
const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor();
auto width = imageDescriptor.m_size.m_width;
const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format);
const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor();
size_t outValuesIndex = 0;
for (uint32_t y = topLeft.second; y < bottomRight.second; ++y)
{
for (uint32_t x = topLeft.first; x < bottomRight.first; ++x)
{
size_t imageDataIndex = (y * width + x) * numComponents + componentIndex;
size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex);
auto& outValue = outValues[outValuesIndex++];
outValue = Internal::RetrieveFloatValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
@@ -510,16 +542,14 @@ namespace AZ
return false;
}
const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor();
auto width = imageDescriptor.m_size.m_width;
const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format);
const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor();
size_t outValuesIndex = 0;
for (uint32_t y = topLeft.second; y < bottomRight.second; ++y)
{
for (uint32_t x = topLeft.first; x < bottomRight.first; ++x)
{
size_t imageDataIndex = (y * width + x) * numComponents + componentIndex;
size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex);
auto& outValue = outValues[outValuesIndex++];
outValue = Internal::RetrieveUintValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
@@ -542,16 +572,14 @@ namespace AZ
return false;
}
const AZ::RHI::ImageDescriptor imageDescriptor = imageAsset->GetImageDescriptor();
auto width = imageDescriptor.m_size.m_width;
const uint32_t numComponents = AZ::RHI::GetFormatComponentCount(imageDescriptor.m_format);
const AZ::RHI::ImageDescriptor& imageDescriptor = imageAsset->GetImageDescriptor();
size_t outValuesIndex = 0;
for (uint32_t y = topLeft.second; y < bottomRight.second; ++y)
{
for (uint32_t x = topLeft.first; x < bottomRight.first; ++x)
{
size_t imageDataIndex = (y * width + x) * numComponents + componentIndex;
size_t imageDataIndex = Internal::GetImageDataIndex(imageDescriptor, x, y, componentIndex);
auto& outValue = outValues[outValuesIndex++];
outValue = Internal::RetrieveIntValue(imageData.data(), imageDataIndex, imageDescriptor.m_format);
@@ -15,6 +15,7 @@
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
namespace AZ
{
@@ -35,6 +36,15 @@ namespace AZ
->Field("MaterialSlots", &ModelAsset::m_materialSlots)
->Field("LodAssets", &ModelAsset::m_lodAssets)
;
// Note: This class needs to have edit context reflection so PropertyAssetCtrl::OnEditButtonClicked
// can open the asset with the preferred asset editor (Scene Settings).
if (auto* editContext = serializeContext->GetEditContext())
{
editContext->Class<ModelAsset>("Model Asset", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
;
}
}
}
@@ -63,10 +63,8 @@ namespace AtomToolsFramework
pipelineDesc.m_mainViewTagName = "MainCamera";
pipelineDesc.m_name = pipelineName;
pipelineDesc.m_rootPassTemplate = "ToolsPipelineRenderToTexture";
pipelineDesc.m_renderSettings.m_multisampleState = AZ::RPI::RPISystemInterface::Get()->GetApplicationMultisampleState();
// We have to set the samples to 4 to match the pipeline passes' setting, otherwise it may lead to device lost issue
// [GFX TODO] [ATOM-13551] Default value sand validation required to prevent pipeline crash and device lost
pipelineDesc.m_renderSettings.m_multisampleState.m_samples = 4;
m_renderPipeline = AZ::RPI::RenderPipeline::CreateRenderPipeline(pipelineDesc);
m_scene->AddRenderPipeline(m_renderPipeline);
m_scene->Activate();
@@ -72,6 +72,9 @@ namespace AZ
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_modelAsset, "Mesh Asset", "Mesh asset reference")
->Attribute(AZ_CRC_CE("EditButton"), "")
->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings")
->Attribute(AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"), true)
->DataElement(AZ::Edit::UIHandlers::Default, &MeshComponentConfig::m_sortKey, "Sort Key", "Transparent meshes are drawn by sort key then depth. Used this to force certain transparent meshes to draw before or after others.")
->Attribute(AZ::Edit::Attributes::Visibility, &MeshComponentConfig::IsAssetSet)
->DataElement(AZ::Edit::UIHandlers::CheckBox, &MeshComponentConfig::m_excludeFromReflectionCubeMaps, "Exclude from reflection cubemaps", "Mesh will not be visible in baked reflection probe cubemaps")
@@ -98,6 +98,8 @@ namespace GradientSignal
void SetupDependencies();
void GetSubImageData();
// ImageGradientRequestBus overrides...
AZStd::string GetImageAssetPath() const override;
void SetImageAssetPath(const AZStd::string& assetPath) override;
@@ -113,5 +115,6 @@ namespace GradientSignal
LmbrCentral::DependencyMonitor m_dependencyMonitor;
mutable AZStd::shared_mutex m_imageMutex;
GradientTransform m_gradientTransform;
AZStd::span<const uint8_t> m_imageData;
};
}
@@ -62,6 +62,6 @@ namespace GradientSignal
}
};
float GetValueFromImageAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue);
float GetValueFromImageAsset(AZStd::span<const uint8_t> imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue);
} // namespace GradientSignal
@@ -215,6 +215,16 @@ namespace GradientSignal
m_dependencyMonitor.ConnectDependency(m_configuration.m_imageAsset.GetId());
}
void ImageGradientComponent::GetSubImageData()
{
if (!m_configuration.m_imageAsset || !m_configuration.m_imageAsset.IsReady())
{
return;
}
m_imageData = m_configuration.m_imageAsset->GetSubImageData(0, 0);
}
void ImageGradientComponent::Activate()
{
// This will immediately call OnGradientTransformChanged and initialize m_gradientTransform.
@@ -224,10 +234,13 @@ namespace GradientSignal
ImageGradientRequestBus::Handler::BusConnect(GetEntityId());
GradientRequestBus::Handler::BusConnect(GetEntityId());
AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId());
AZStd::unique_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
// Invoke the QueueLoad before connecting to the AssetBus, so that
// if the asset is already ready, then OnAssetReady will be triggered immediately
m_imageData = AZStd::span<const uint8_t>();
m_configuration.m_imageAsset.QueueLoad();
AZ::Data::AssetBus::Handler::BusConnect(m_configuration.m_imageAsset.GetId());
}
void ImageGradientComponent::Deactivate()
@@ -267,18 +280,24 @@ namespace GradientSignal
{
AZStd::unique_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
m_configuration.m_imageAsset = asset;
GetSubImageData();
}
void ImageGradientComponent::OnAssetMoved(AZ::Data::Asset<AZ::Data::AssetData> asset, [[maybe_unused]] void* oldDataPointer)
{
AZStd::unique_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
m_configuration.m_imageAsset = asset;
GetSubImageData();
}
void ImageGradientComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
AZStd::unique_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
m_configuration.m_imageAsset = asset;
GetSubImageData();
}
void ImageGradientComponent::OnGradientTransformChanged(const GradientTransform& newTransform)
@@ -292,6 +311,12 @@ namespace GradientSignal
AZ::Vector3 uvw = sampleParams.m_position;
bool wasPointRejected = false;
// Return immediately if our cached image data hasn't been retrieved yet
if (m_imageData.empty())
{
return 0.0f;
}
{
AZStd::shared_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
@@ -300,7 +325,7 @@ namespace GradientSignal
if (!wasPointRejected)
{
return GetValueFromImageAsset(
m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f);
m_imageData, m_configuration.m_imageAsset->GetImageDescriptor(), uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f);
}
}
@@ -315,6 +340,12 @@ namespace GradientSignal
return;
}
// Return immediately if our cached image data hasn't been retrieved yet
if (m_imageData.empty())
{
return;
}
AZ::Vector3 uvw;
bool wasPointRejected = false;
@@ -327,7 +358,7 @@ namespace GradientSignal
if (!wasPointRejected)
{
outValues[index] = GetValueFromImageAsset(
m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f);
m_imageData, m_configuration.m_imageAsset->GetImageDescriptor(), uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f);
}
else
{
@@ -353,6 +384,10 @@ namespace GradientSignal
{
AZStd::unique_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
// Clear our cached image data
m_imageData = AZStd::span<const uint8_t>();
m_configuration.m_imageAsset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, azrtti_typeid<AZ::RPI::StreamingImageAsset>(), m_configuration.m_imageAsset.GetAutoLoadBehavior());
}
@@ -11,6 +11,7 @@
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <LmbrCentral/Dependency/DependencyNotificationBus.h>
#include <GradientSignal/Ebuses/GradientPreviewRequestBus.h>
namespace GradientSignal
{
@@ -61,6 +62,12 @@ namespace GradientSignal
void EditorGradientSurfaceDataComponent::Deactivate()
{
// Make sure any previews for this entity aren't currently trying to refresh. Otherwise, the preview job could call
// back into our FilterFunc lambda below after the entity has already been destroyed.
AZ::EntityId canceledEntity;
GradientSignal::GradientPreviewRequestBus::EventResult(
canceledEntity, GetEntityId(), &GradientSignal::GradientPreviewRequestBus::Events::CancelRefresh);
// If the preview shouldn't be active, use an invalid entityId
m_gradientEntityId = AZ::EntityId();
AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
@@ -78,11 +78,10 @@ namespace GradientSignal
return true;
}
float GetValueFromImageAsset(const AZ::Data::Asset<AZ::RPI::StreamingImageAsset>& imageAsset, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue)
float GetValueFromImageAsset(AZStd::span<const uint8_t> imageData, const AZ::RHI::ImageDescriptor& imageDescriptor, const AZ::Vector3& uvw, float tilingX, float tilingY, float defaultValue)
{
if (imageAsset.IsReady())
if (!imageData.empty())
{
auto imageDescriptor = imageAsset->GetImageDescriptor();
auto width = imageDescriptor.m_size.m_width;
auto height = imageDescriptor.m_size.m_height;
@@ -125,7 +124,7 @@ namespace GradientSignal
// Flip the y because images are stored in reverse of our world axes
y = (height - 1) - y;
return AZ::RPI::GetSubImagePixelValue<float>(imageAsset, x, y);
return AZ::RPI::GetImageDataPixelValue<float>(imageData, imageDescriptor, x, y);
}
}
@@ -90,7 +90,7 @@ namespace UnitTest
AZStd::unique_ptr<AZ::Entity> BuildTestSurfaceMaskGradient(float shapeHalfBounds);
AZStd::unique_ptr<AZ::Entity> BuildTestSurfaceSlopeGradient(float shapeHalfBounds);
AZ::RPI::AssetHandlerPtrList m_assetHandlers;
AZStd::fixed_vector<AZStd::unique_ptr<AZ::Data::AssetHandler>, 2> m_assetHandlers;
};
struct GradientSignalTest
+6 -2
View File
@@ -387,7 +387,9 @@ void HierarchyMenu::New_ElementFromSlice(HierarchyWidget* hierarchy,
AZ::Vector2 viewportPosition(-1.0f,-1.0f); // indicates no viewport position specified
if (optionalPos)
{
viewportPosition = QtHelpers::QPointFToVector2(*optionalPos);
// Convert position to render viewport coords
QPointF scaledPosition = *optionalPos * hierarchy->GetEditorWindow()->GetViewport()->WidgetToViewportFactor();
viewportPosition = QtHelpers::QPointFToVector2(scaledPosition);
}
SliceMenuHelpers::CreateInstantiateSliceMenu(hierarchy,
@@ -405,7 +407,9 @@ void HierarchyMenu::New_ElementFromSlice(HierarchyWidget* hierarchy,
AZ::Vector2 viewportPosition(-1.0f,-1.0f); // indicates no viewport position specified
if (optionalPos)
{
viewportPosition = QtHelpers::QPointFToVector2(*optionalPos);
// Convert position to render viewport coords
QPointF scaledPosition = *optionalPos * hierarchy->GetEditorWindow()->GetViewport()->WidgetToViewportFactor();
viewportPosition = QtHelpers::QPointFToVector2(scaledPosition);
}
hierarchy->GetEditorWindow()->GetSliceManager()->InstantiateSliceUsingBrowser(hierarchy, viewportPosition);
}
+4 -2
View File
@@ -1239,11 +1239,13 @@ void HierarchyWidget::AddElement(const QTreeWidgetItemRawPtrQList& selectedItems
this,
selectedItems,
childIndex,
[optionalPos](AZ::Entity* element)
[this, optionalPos](AZ::Entity* element)
{
if (optionalPos)
{
EntityHelpers::MoveElementToGlobalPosition(element, *optionalPos);
// Convert position to render viewport coords
QPoint scaledPosition = *optionalPos * GetEditorWindow()->GetViewport()->WidgetToViewportFactor();
EntityHelpers::MoveElementToGlobalPosition(element, scaledPosition);
}
});
}
+21 -21
View File
@@ -121,27 +121,27 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_googletest(
NAME Gem::MotionMatching.Tests
)
endif()
# If we are a host platform we want to add tools test like editor tests here
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME MotionMatching.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
motionmatching_editor_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
Gem::MotionMatching.Editor
)
# If we are a host platform we want to add tools test like editor tests here
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME MotionMatching.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Gem
FILES_CMAKE
motionmatching_editor_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
Tests
Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
Gem::MotionMatching.Editor
)
# Add MotionMatching.Editor.Tests to googletest
ly_add_googletest(
NAME Gem::MotionMatching.Editor.Tests
)
# Add MotionMatching.Editor.Tests to googletest
ly_add_googletest(
NAME Gem::MotionMatching.Editor.Tests
)
endif()
endif()
@@ -122,43 +122,41 @@ namespace Multiplayer
{
AZ::Entity* entity = static_cast<AZ::Entity*>(visEntry->m_userData);
NetworkEntityHandle entityHandle(entity, networkEntityTracker);
if (entityHandle.GetNetBindComponent() == nullptr)
if (entityHandle.GetNetBindComponent() != nullptr)
{
// Not a net-bound entity, terminate processing of this entity
return;
}
const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityWorldBoundsUnion(entity->GetId());
const AZ::Vector3 currentCenter = currentBounds.GetCenter();
NetworkTransformComponent* networkTransform = entity->template FindComponent<NetworkTransformComponent>();
if (debugDisplay)
{
debugDisplay->SetColor(AZ::Colors::White);
debugDisplay->DrawWireBox(currentBounds.GetMin(), currentBounds.GetMax());
}
if (networkTransform != nullptr)
{
// Get the rewound position for target host frame ID plus the one preceding it for potential lerp
AZ::Vector3 rewindCenter = networkTransform->GetTranslation();
const AZ::Vector3 rewindCenterPrevious = networkTransform->GetTranslationPrevious();
const float blendFactor = GetNetworkTime()->GetHostBlendFactor();
if (!AZ::IsClose(blendFactor, 1.0f) && !rewindCenter.IsClose(rewindCenterPrevious))
{
// If we have a blend factor, lerp the translation for accuracy
rewindCenter = rewindCenterPrevious.Lerp(rewindCenter, blendFactor);
}
const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions
const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb
const AZ::Aabb currentBounds = entityBoundsUnion->GetEntityWorldBoundsUnion(entity->GetId());
const AZ::Vector3 currentCenter = currentBounds.GetCenter();
NetworkTransformComponent* networkTransform = entity->template FindComponent<NetworkTransformComponent>();
if (debugDisplay)
{
debugDisplay->SetColor(AZ::Colors::Grey);
debugDisplay->DrawWireBox(rewoundAabb.GetMin(), rewoundAabb.GetMax());
debugDisplay->SetColor(AZ::Colors::White);
debugDisplay->DrawWireBox(currentBounds.GetMin(), currentBounds.GetMax());
}
if (AZ::ShapeIntersection::Overlaps(rewoundAabb, rewindVolume)) // Validate the rewound aabb intersects our rewind volume
if (networkTransform != nullptr)
{
m_rewoundEntities.push_back(entityHandle);
// Get the rewound position for target host frame ID plus the one preceding it for potential lerp
AZ::Vector3 rewindCenter = networkTransform->GetTranslation();
const AZ::Vector3 rewindCenterPrevious = networkTransform->GetTranslationPrevious();
const float blendFactor = GetNetworkTime()->GetHostBlendFactor();
if (!AZ::IsClose(blendFactor, 1.0f) && !rewindCenter.IsClose(rewindCenterPrevious))
{
// If we have a blend factor, lerp the translation for accuracy
rewindCenter = rewindCenterPrevious.Lerp(rewindCenter, blendFactor);
}
const AZ::Vector3 rewindOffset = rewindCenter - currentCenter; // Compute offset between rewound and current positions
const AZ::Aabb rewoundAabb = currentBounds.GetTranslated(rewindOffset); // Apply offset to the entity aabb
if (debugDisplay)
{
debugDisplay->SetColor(AZ::Colors::Grey);
debugDisplay->DrawWireBox(rewoundAabb.GetMin(), rewoundAabb.GetMax());
}
if (AZ::ShapeIntersection::Overlaps(rewoundAabb, rewindVolume)) // Validate the rewound aabb intersects our rewind volume
{
m_rewoundEntities.push_back(entityHandle);
entityHandle.GetNetBindComponent()->NotifySyncRewindState();
}
}
}
}
@@ -60,6 +60,7 @@ namespace PhysX
"Specifies the PhysX mesh collider asset for this PhysX collider component.")
->Attribute(AZ_CRC_CE("EditButton"), "")
->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings")
->Attribute(AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"), true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_configuration, "Configuration",
"PhysX mesh asset collider configuration.")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
@@ -5,7 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <ScriptCanvas/Asset/RuntimeAsset.h>
#include <ScriptCanvas/Components/EditorScriptCanvasComponentSerializer.h>
@@ -17,7 +17,7 @@ namespace AZ
JsonSerializationResult::Result EditorScriptCanvasComponentSerializer::Load
( void* outputValue
, const Uuid& outputValueTypeId
, [[maybe_unused]] const Uuid& outputValueTypeId
, const rapidjson::Value& inputValue
, JsonDeserializerContext& context)
{
@@ -32,9 +32,7 @@ namespace AZ
JsonSerializationResult::ResultCode result = BaseJsonSerializer::Load(outputValue
, azrtti_typeid<AzToolsFramework::Components::EditorComponentBase>(), inputValue, context);
// load child data one by one...
result.Combine(BaseJsonSerializer::Load(outputValue, outputValueTypeId, inputValue, context));
// load child data one by one
if (result.GetProcessing() != JSR::Processing::Halted)
{
result.Combine(ContinueLoadingFromJsonObjectField
@@ -29,12 +29,6 @@ namespace SurfaceData
class SurfaceTagWeights
{
public:
//! The maximum number of surface weights that we can store.
//! For performance reasons, we want to limit this so that we can preallocate the max size in advance.
//! The current number is chosen to be higher than expected needs, but small enough to avoid being excessively wasteful.
//! (Dynamic structures would end up taking more memory than what we're preallocating)
static inline constexpr size_t MaxSurfaceWeights = 16;
SurfaceTagWeights() = default;
//! Construct a collection of SurfaceTagWeights from the given SurfaceTagWeightList.
@@ -65,7 +59,7 @@ namespace SurfaceData
// early-out once we pass the location for the entry instead of always searching every entry.
if (weightItr->m_surfaceType > tag)
{
if (m_weights.size() != MaxSurfaceWeights)
if (m_weights.size() != AzFramework::SurfaceData::Constants::MaxSurfaceWeights)
{
// We didn't find the surface type, so add the new entry in sorted order.
m_weights.insert(weightItr, { tag, weight });
@@ -85,7 +79,7 @@ namespace SurfaceData
}
// We didn't find the surface weight, and the sort order for it is at the end, so add it to the back of the list.
if (m_weights.size() != MaxSurfaceWeights)
if (m_weights.size() != AzFramework::SurfaceData::Constants::MaxSurfaceWeights)
{
m_weights.emplace_back(tag, weight);
}
@@ -188,7 +182,7 @@ namespace SurfaceData
//! @return The pointer to the tag that's found, or end() if it wasn't found.
const AzFramework::SurfaceData::SurfaceTagWeight* FindTag(AZ::Crc32 tag) const;
AZStd::fixed_vector<AzFramework::SurfaceData::SurfaceTagWeight, MaxSurfaceWeights> m_weights;
AZStd::fixed_vector<AzFramework::SurfaceData::SurfaceTagWeight, AzFramework::SurfaceData::Constants::MaxSurfaceWeights> m_weights;
};
//! SurfacePointList stores a collection of surface point data, which consists of positions, normals, and surface tag weights.
@@ -19,7 +19,8 @@ namespace SurfaceData
{
public:
AZ_CLASS_ALLOCATOR(SurfaceTag, AZ::SystemAllocator, 0);
AZ_RTTI(SurfaceTag, "{67C8C6ED-F32A-443E-A777-1CAE48B22CD7}");
AZ_TYPE_INFO(SurfaceTag, "{67C8C6ED-F32A-443E-A777-1CAE48B22CD7}");
static void Reflect(AZ::ReflectContext* context);
SurfaceTag()
@@ -47,6 +48,8 @@ namespace SurfaceData
m_surfaceTagCrc = AZ::Crc32(value.data());
}
AZStd::string GetDisplayName() const;
static AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> GetRegisteredTags();
private:
@@ -54,8 +57,6 @@ namespace SurfaceData
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> BuildSelectableTagList() const;
AZStd::string GetDisplayName() const;
AZ::u32 m_surfaceTagCrc;
};
@@ -42,7 +42,7 @@ namespace SurfaceData
AzFramework::SurfaceData::SurfaceTagWeightList SurfaceTagWeights::GetSurfaceTagWeightList() const
{
AzFramework::SurfaceData::SurfaceTagWeightList weights;
weights.reserve(m_weights.size());
for (auto& weight : m_weights)
{
weights.emplace_back(weight);
@@ -275,7 +275,7 @@ namespace UnitTest
{
AZ_PROFILE_FUNCTION(Entity);
AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights];
AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights];
AZ::SimpleLcgRandom randomGenerator(1234567);
// Declare this outside the loop so that we aren't benchmarking creation and destruction.
@@ -316,7 +316,7 @@ namespace UnitTest
{
AZ_PROFILE_FUNCTION(Entity);
AZ::Crc32 tags[SurfaceData::SurfaceTagWeights::MaxSurfaceWeights];
AZ::Crc32 tags[AzFramework::SurfaceData::Constants::MaxSurfaceWeights];
AZ::SimpleLcgRandom randomGenerator(1234567);
// Declare this outside the loop so that we aren't benchmarking creation and destruction.
@@ -22,6 +22,7 @@
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
namespace Terrain
{
void TerrainPhysicsSurfaceMaterialMapping::Reflect(AZ::ReflectContext* context)
@@ -32,27 +33,10 @@ namespace Terrain
->Version(1)
->Field("Surface", &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag)
->Field("Material", &TerrainPhysicsSurfaceMaterialMapping::m_materialId);
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainPhysicsSurfaceMaterialMapping>(
"Terrain Surface Material Mapping", "Mapping between a surface and a physics material.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag, "Surface Tag",
"Surface type to map to a physics material.")
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsSurfaceMaterialMapping::m_materialId, "Material ID", "")
->ElementAttribute(Physics::Attributes::MaterialLibraryAssetId, &TerrainPhysicsSurfaceMaterialMapping::GetMaterialLibraryId)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true);
}
}
}
AZ::Data::AssetId TerrainPhysicsSurfaceMaterialMapping::GetMaterialLibraryId()
{
if (const auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
@@ -76,22 +60,6 @@ namespace Terrain
->Field("DefaultMaterial", &TerrainPhysicsColliderConfig::m_defaultMaterialSelection)
->Field("Mappings", &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings)
;
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainPhysicsColliderConfig>(
"Terrain Physics Collider Component",
"Provides terrain data to a physics collider with configurable surface mappings.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_defaultMaterialSelection,
"Default Surface Physics Material", "Select a material to be used by unmapped surfaces by default")
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings,
"Surface to Material Mappings", "Maps surfaces to physics materials")
;
}
}
}
@@ -25,6 +25,8 @@ namespace LmbrCentral
namespace Terrain
{
class EditorSurfaceTagListProvider;
static const uint8_t InvalidSurfaceTagIndex = 0xFF;
struct TerrainPhysicsSurfaceMaterialMapping final
@@ -33,12 +35,16 @@ namespace Terrain
AZ_CLASS_ALLOCATOR(TerrainPhysicsSurfaceMaterialMapping, AZ::SystemAllocator, 0);
AZ_RTTI(TerrainPhysicsSurfaceMaterialMapping, "{A88B5289-DFCD-4564-8395-E2177DFE5B18}");
static void Reflect(AZ::ReflectContext* context);
static AZ::Data::AssetId GetMaterialLibraryId();
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> BuildSelectableTagList() const;
void SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider);
SurfaceData::SurfaceTag m_surfaceTag;
Physics::MaterialId m_materialId;
private:
static AZ::Data::AssetId GetMaterialLibraryId();
const EditorSurfaceTagListProvider* m_tagListProvider = nullptr;
};
class TerrainPhysicsColliderConfig
@@ -48,6 +54,7 @@ namespace Terrain
AZ_CLASS_ALLOCATOR(TerrainPhysicsColliderConfig, AZ::SystemAllocator, 0);
AZ_RTTI(TerrainPhysicsColliderConfig, "{E9EADB8F-C3A5-4B9C-A62D-2DBC86B4CE59}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
Physics::MaterialSelection m_defaultMaterialSelection;
AZStd::vector<TerrainPhysicsSurfaceMaterialMapping> m_surfaceMaterialMappings;
};
@@ -26,26 +26,6 @@ namespace Terrain
->Field("Gradient Entity", &TerrainSurfaceGradientMapping::m_gradientEntityId)
->Field("Surface Tag", &TerrainSurfaceGradientMapping::m_surfaceTag)
;
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainSurfaceGradientMapping>("Terrain Surface Gradient Mapping", "Mapping between a gradient and a surface.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_gradientEntityId,
"Gradient Entity", "ID of Entity providing a gradient.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->UIElement("GradientPreviewer", "Previewer")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
->Attribute(AZ_CRC_CE("GradientEntity"), &TerrainSurfaceGradientMapping::m_gradientEntityId)
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_surfaceTag, "Surface Tag",
"Surface type to map to this gradient.")
;
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
@@ -71,21 +51,6 @@ namespace Terrain
->Version(1)
->Field("Mappings", &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings)
;
AZ::EditContext* edit = serialize->GetEditContext();
if (edit)
{
edit->Class<TerrainSurfaceGradientListConfig>(
"Terrain Surface Gradient List Component", "Provide mapping between gradients and surfaces.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings,
"Gradient to Surface Mappings", "Maps Gradient Entities to Surfaces.")
;
}
}
}
@@ -25,6 +25,8 @@ namespace LmbrCentral
namespace Terrain
{
class EditorSurfaceTagListProvider;
class TerrainSurfaceGradientMapping final
{
public:
@@ -39,8 +41,14 @@ namespace Terrain
{
}
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> BuildSelectableTagList() const;
void SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider);
AZ::EntityId m_gradientEntityId;
SurfaceData::SurfaceTag m_surfaceTag;
private:
const EditorSurfaceTagListProvider* m_tagListProvider = nullptr;
};
class TerrainSurfaceGradientListConfig : public AZ::ComponentConfig
@@ -20,5 +20,85 @@ namespace Terrain
&LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType,
typename BaseClassType::WrappedConfigType, 1>
);
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainPhysicsSurfaceMaterialMapping>(
"Terrain Surface Material Mapping", "Mapping between a surface and a physics material.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->DataElement(
AZ::Edit::UIHandlers::ComboBox, &TerrainPhysicsSurfaceMaterialMapping::m_surfaceTag, "Surface Tag",
"Surface type to map to a physics material.")
->Attribute(AZ::Edit::Attributes::EnumValues, &TerrainPhysicsSurfaceMaterialMapping::BuildSelectableTagList)
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsSurfaceMaterialMapping::m_materialId, "Material ID", "")
->ElementAttribute(Physics::Attributes::MaterialLibraryAssetId, &TerrainPhysicsSurfaceMaterialMapping::GetMaterialLibraryId)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true)
;
edit->Class<TerrainPhysicsColliderConfig>(
"Terrain Physics Collider Component",
"Provides terrain data to a physics collider with configurable surface mappings.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_defaultMaterialSelection,
"Default Surface Physics Material", "Select a material to be used by unmapped surfaces by default")
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainPhysicsColliderConfig::m_surfaceMaterialMappings,
"Surface to Material Mappings", "Maps surfaces to physics materials")
;
}
}
}
void EditorTerrainPhysicsColliderComponent::Activate()
{
UpdateConfigurationTagProvider();
BaseClassType::Activate();
}
AZStd::unordered_set<SurfaceData::SurfaceTag> EditorTerrainPhysicsColliderComponent::GetSurfaceTagsInUse() const
{
AZStd::unordered_set<SurfaceData::SurfaceTag> tagsInUse;
for (const TerrainPhysicsSurfaceMaterialMapping& mapping : m_configuration.m_surfaceMaterialMappings)
{
tagsInUse.insert(mapping.m_surfaceTag);
}
return AZStd::move(tagsInUse);
}
AZ::u32 EditorTerrainPhysicsColliderComponent::ConfigurationChanged()
{
UpdateConfigurationTagProvider();
return BaseClassType::ConfigurationChanged();
}
void EditorTerrainPhysicsColliderComponent::UpdateConfigurationTagProvider()
{
for (TerrainPhysicsSurfaceMaterialMapping& mapping : m_configuration.m_surfaceMaterialMappings)
{
mapping.SetTagListProvider(this);
}
}
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> TerrainPhysicsSurfaceMaterialMapping::BuildSelectableTagList() const
{
AZ_PROFILE_FUNCTION(Entity);
return AZStd::move(Terrain::BuildSelectableTagList(m_tagListProvider, m_surfaceTag));
}
void TerrainPhysicsSurfaceMaterialMapping::SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider)
{
m_tagListProvider = tagListProvider;
}
}
@@ -11,22 +11,34 @@
#include <Components/TerrainPhysicsColliderComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
#include <EditorSurfaceTagListProvider.h>
namespace Terrain
{
class EditorTerrainPhysicsColliderComponent
: public LmbrCentral::EditorWrappedComponentBase<TerrainPhysicsColliderComponent, TerrainPhysicsColliderConfig>
, public EditorSurfaceTagListProvider
{
public:
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<TerrainPhysicsColliderComponent, TerrainPhysicsColliderConfig>;
AZ_EDITOR_COMPONENT(EditorTerrainPhysicsColliderComponent, "{C43FAB8F-3968-46A6-920E-E84AEDED3DF5}", BaseClassType);
static void Reflect(AZ::ReflectContext* context);
// AZ::Component interface implementation
void Activate() override;
static constexpr auto s_categoryName = "Terrain";
static constexpr auto s_componentName = "Terrain Physics Heightfield Collider";
static constexpr auto s_componentDescription = "Provides terrain data to a physics collider in the form of a heightfield and surface->material mapping.";
static constexpr auto s_icon = "Editor/Icons/Components/TerrainPhysicsCollider.svg";
static constexpr auto s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainPhysicsCollider.svg";
static constexpr auto s_helpUrl = "";
private:
// EditorSurfaceTagListProvider interface implementation
AZStd::unordered_set<SurfaceData::SurfaceTag> GetSurfaceTagsInUse() const override;
AZ::u32 ConfigurationChanged() override;
void UpdateConfigurationTagProvider();
};
}
@@ -19,5 +19,83 @@ namespace Terrain
&LmbrCentral::EditorWrappedComponentBaseVersionConverter<typename BaseClassType::WrappedComponentType,
typename BaseClassType::WrappedConfigType, 1>
);
if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainSurfaceGradientMapping>("Terrain Surface Gradient Mapping", "Mapping between a gradient and a surface.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_gradientEntityId,
"Gradient Entity", "ID of Entity providing a gradient.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->UIElement("GradientPreviewer", "Previewer")
->Attribute(AZ::Edit::Attributes::NameLabelOverride, "")
->Attribute(AZ_CRC_CE("GradientEntity"), &TerrainSurfaceGradientMapping::m_gradientEntityId)
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientMapping::m_surfaceTag, "Surface Tag",
"Surface type to map to this gradient.")
->Attribute(AZ::Edit::Attributes::EnumValues, &TerrainSurfaceGradientMapping::BuildSelectableTagList)
;
edit->Class<TerrainSurfaceGradientListConfig>(
"Terrain Surface Gradient List Component", "Provide mapping between gradients and surfaces.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceGradientListConfig::m_gradientSurfaceMappings,
"Gradient to Surface Mappings", "Maps Gradient Entities to Surfaces.")
;
}
}
}
void EditorTerrainSurfaceGradientListComponent::Activate()
{
UpdateConfigurationTagProvider();
BaseClassType::Activate();
}
AZ::u32 EditorTerrainSurfaceGradientListComponent::ConfigurationChanged()
{
UpdateConfigurationTagProvider();
return BaseClassType::ConfigurationChanged();
}
void EditorTerrainSurfaceGradientListComponent::UpdateConfigurationTagProvider()
{
for (TerrainSurfaceGradientMapping& mapping : m_configuration.m_gradientSurfaceMappings)
{
mapping.SetTagListProvider(this);
}
}
AZStd::unordered_set<SurfaceData::SurfaceTag> EditorTerrainSurfaceGradientListComponent::GetSurfaceTagsInUse() const
{
AZStd::unordered_set<SurfaceData::SurfaceTag> tagsInUse;
for (const TerrainSurfaceGradientMapping& mapping : m_configuration.m_gradientSurfaceMappings)
{
tagsInUse.insert(mapping.m_surfaceTag);
}
return AZStd::move(tagsInUse);
}
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> TerrainSurfaceGradientMapping::BuildSelectableTagList() const
{
AZ_PROFILE_FUNCTION(Entity);
return AZStd::move(Terrain::BuildSelectableTagList(m_tagListProvider, m_surfaceTag));
}
void TerrainSurfaceGradientMapping::SetTagListProvider(const EditorSurfaceTagListProvider* tagListProvider)
{
m_tagListProvider = tagListProvider;
}
}
@@ -11,22 +11,34 @@
#include <Components/TerrainSurfaceGradientListComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <LmbrCentral/Component/EditorWrappedComponentBase.h>
#include <EditorSurfaceTagListProvider.h>
namespace Terrain
{
class EditorTerrainSurfaceGradientListComponent
: public LmbrCentral::EditorWrappedComponentBase<TerrainSurfaceGradientListComponent, TerrainSurfaceGradientListConfig>
, public EditorSurfaceTagListProvider
{
public:
using BaseClassType = LmbrCentral::EditorWrappedComponentBase<TerrainSurfaceGradientListComponent, TerrainSurfaceGradientListConfig>;
AZ_EDITOR_COMPONENT(EditorTerrainSurfaceGradientListComponent, "{49831E91-A11F-4EFF-A824-6D85C284B934}", BaseClassType);
static void Reflect(AZ::ReflectContext* context);
// AZ::Component interface implementation
void Activate() override;
static constexpr const char* const s_categoryName = "Terrain";
static constexpr const char* const s_componentName = "Terrain Surface Gradient List";
static constexpr const char* const s_componentDescription = "Provides a mapping between gradients and surface tags for use by the terrain system.";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceGradientList.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg";
static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/surface-gradient-list/";
private:
// EditorSurfaceTagListProvider interface implementation
AZStd::unordered_set<SurfaceData::SurfaceTag> GetSurfaceTagsInUse() const override;
AZ::u32 ConfigurationChanged() override;
void UpdateConfigurationTagProvider();
};
}
@@ -0,0 +1,41 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <EditorSurfaceTagListProvider.h>
#include <AzCore/std/sort.h>
namespace Terrain
{
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> BuildSelectableTagList(const EditorSurfaceTagListProvider* tagListProvider,
const SurfaceData::SurfaceTag& currentTag)
{
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> availableTags = SurfaceData::SurfaceTag::GetRegisteredTags();
AZStd::unordered_set<SurfaceData::SurfaceTag> tagsInUse;
if (tagListProvider)
{
tagsInUse = AZStd::move(tagListProvider->GetSurfaceTagsInUse());
// Filter out all tags in use from the list of registered tags
AZStd::erase_if(availableTags, [&tagsInUse](const auto& tag)-> bool
{
return tagsInUse.contains(SurfaceData::SurfaceTag(tag.first));
});
}
// Insert the current tag back if it was removed via tagsInUse
availableTags.emplace_back(AZ::u32(currentTag), currentTag.GetDisplayName());
// Sorting for consistency
AZStd::sort(availableTags.begin(), availableTags.end(), [](const auto& lhs, const auto& rhs) {return lhs.second < rhs.second; });
return AZStd::move(availableTags);
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/unordered_set.h>
#include <SurfaceData/SurfaceTag.h>
namespace Terrain
{
//! Interface for a class providing information about surface tags available for selecting in Editor components.
class EditorSurfaceTagListProvider
{
public:
//! Returns a set of all surface tags currently in use that won't be available for selecting.
virtual AZStd::unordered_set<SurfaceData::SurfaceTag> GetSurfaceTagsInUse() const = 0;
};
//! Returns a list of available tags to be selected in the component.
AZStd::vector<AZStd::pair<AZ::u32, AZStd::string>> BuildSelectableTagList(
const EditorSurfaceTagListProvider* tagListProvider,
const SurfaceData::SurfaceTag& currentTag);
}
@@ -8,21 +8,19 @@
#include <TerrainRenderer/Components/TerrainSurfaceMaterialsListComponent.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Asset/AssetSerializer.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <SurfaceData/SurfaceDataProviderRequestBus.h>
#include <TerrainSystem/TerrainSystemBus.h>
namespace Terrain
@@ -38,15 +36,14 @@ namespace Terrain
if (auto edit = serialize->GetEditContext())
{
edit->Class<TerrainSurfaceMaterialMapping>("Terrain Surface Gradient Mapping", "Mapping between a surface and a material.")
edit->Class<TerrainSurfaceMaterialMapping>("Terrain surface gradient mapping", "Mapping between a surface and a material.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainSurfaceMaterialMapping::m_surfaceTag, "Surface Tag", "Surface type to map to a material.")
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialMapping::m_materialAsset, "Material Asset", "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &TerrainSurfaceMaterialMapping::m_surfaceTag, "Surface tag", "Surface type to map to a material.")
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialMapping::m_materialAsset, "Material asset", "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ShowProductAssetFileName, true)
;
}
}
@@ -60,7 +57,8 @@ namespace Terrain
if (serialize)
{
serialize->Class<TerrainSurfaceMaterialsListConfig, AZ::ComponentConfig>()
->Version(1)
->Version(2)
->Field("DefaultMaterial", &TerrainSurfaceMaterialsListConfig::m_defaultSurfaceMaterial)
->Field("Mappings", &TerrainSurfaceMaterialsListConfig::m_surfaceMaterials);
AZ::EditContext* edit = serialize->GetEditContext();
@@ -68,10 +66,13 @@ namespace Terrain
{
edit->Class<TerrainSurfaceMaterialsListConfig>(
"Terrain Surface Material List Component", "Provide mapping between surfaces and render materials.")
->SetDynamicEditDataProvider(&TerrainSurfaceMaterialsListConfig::GetDynamicData)
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialsListConfig::m_defaultSurfaceMaterial,
"Default Material", "The default material to fall back to where no other material surface mappings exist.")
->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainSurfaceMaterialsListConfig::m_surfaceMaterials,
"Material Mappings", "Maps surfaces to materials.");
@@ -79,6 +80,26 @@ namespace Terrain
}
}
TerrainSurfaceMaterialsListConfig::TerrainSurfaceMaterialsListConfig()
{
m_hideSurfaceTagData.m_attributes.push_back(
{
AZ::Edit::Attributes::Visibility,
aznew AZ::Edit::AttributeData<AZ::Crc32>(AZ::Edit::PropertyVisibility::Hide)
}
);
}
const AZ::Edit::ElementData* TerrainSurfaceMaterialsListConfig::GetDynamicData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& )
{
const TerrainSurfaceMaterialsListConfig* owner = reinterpret_cast<const TerrainSurfaceMaterialsListConfig*>(handlerPtr);
if (elementPtr == &owner->m_defaultSurfaceMaterial.m_surfaceTag)
{
return &owner->m_hideSurfaceTagData;
}
return nullptr;
}
void TerrainSurfaceMaterialsListComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
{
services.push_back(AZ_CRC_CE("TerrainMaterialProviderService"));
@@ -116,15 +137,21 @@ namespace Terrain
{
m_cachedAabb = AZ::Aabb::CreateNull();
auto checkLoadMaterial = [&](TerrainSurfaceMaterialMapping& material)
{
if (material.m_materialAsset.GetId().IsValid())
{
material.m_active = false;
material.m_materialAsset.QueueLoad();
AZ::Data::AssetBus::MultiHandler::BusConnect(material.m_materialAsset.GetId());
}
};
// Set all the materials as inactive and start loading.
checkLoadMaterial(m_configuration.m_defaultSurfaceMaterial);
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid())
{
surfaceMaterialMapping.m_active = false;
surfaceMaterialMapping.m_materialAsset.QueueLoad();
AZ::Data::AssetBus::MultiHandler::BusConnect(surfaceMaterialMapping.m_materialAsset.GetId());
}
checkLoadMaterial(surfaceMaterialMapping);
}
// Announce initial shape using OnShapeChanged
@@ -135,24 +162,35 @@ namespace Terrain
{
TerrainAreaMaterialRequestBus::Handler::BusDisconnect();
auto checkResetMaterial = [&](TerrainSurfaceMaterialMapping& material)
{
if (material.m_materialAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(material.m_materialAsset.GetId());
material.m_materialAsset.Release();
material.m_materialInstance.reset();
material.m_activeMaterialAssetId = AZ::Data::AssetId();
}
};
checkResetMaterial(m_configuration.m_defaultSurfaceMaterial);
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (surfaceMaterialMapping.m_materialAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_materialAsset.GetId());
surfaceMaterialMapping.m_materialAsset.Release();
surfaceMaterialMapping.m_materialInstance.reset();
surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId();
}
checkResetMaterial(surfaceMaterialMapping);
}
HandleMaterialStateChanges();
}
int TerrainSurfaceMaterialsListComponent::CountMaterialIDInstances(AZ::Data::AssetId id) const
int TerrainSurfaceMaterialsListComponent::CountMaterialIdInstances(AZ::Data::AssetId id) const
{
int count = 0;
if (m_configuration.m_defaultSurfaceMaterial.m_activeMaterialAssetId == id)
{
count++;
}
for (const auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (surfaceMaterialMapping.m_activeMaterialAssetId == id)
@@ -169,28 +207,63 @@ namespace Terrain
bool anyMaterialIsActive = false;
bool anyMaterialWasAlreadyActive = false;
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
const bool wasPreviouslyActive = surfaceMaterialMapping.m_active;
const bool isNowActive = (surfaceMaterialMapping.m_materialInstance != nullptr);
// Handle default material first
auto& defaultMaterial = m_configuration.m_defaultSurfaceMaterial;
if (wasPreviouslyActive)
{
anyMaterialWasAlreadyActive = true;
}
const bool wasPreviouslyActive = defaultMaterial.m_active;
defaultMaterial.m_active = (defaultMaterial.m_materialInstance != nullptr);
if (isNowActive)
{
anyMaterialIsActive = true;
}
surfaceMaterialMapping.m_active = isNowActive;
if (!wasPreviouslyActive && !isNowActive)
anyMaterialWasAlreadyActive = wasPreviouslyActive;
anyMaterialIsActive = defaultMaterial.m_active;
if (!wasPreviouslyActive && !defaultMaterial.m_active)
{
// A material has been assigned but has not yet completed loading.
}
else if (!wasPreviouslyActive && isNowActive)
else if (!wasPreviouslyActive && defaultMaterial.m_active)
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialCreated, GetEntityId(),
defaultMaterial.m_materialInstance);
defaultMaterial.m_previousChangeId = defaultMaterial.m_materialInstance->GetCurrentChangeId();
}
else if (wasPreviouslyActive && !defaultMaterial.m_active)
{
// Don't disconnect from the AssetBus if this material is mapped more than once.
if (CountMaterialIdInstances(defaultMaterial.m_activeMaterialAssetId) == 1)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(defaultMaterial.m_activeMaterialAssetId);
}
defaultMaterial = {};
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialDestroyed, GetEntityId());
}
else if (defaultMaterial.m_materialInstance->GetAssetId() != defaultMaterial.m_activeMaterialAssetId ||
defaultMaterial.m_materialInstance->GetCurrentChangeId() != defaultMaterial.m_previousChangeId)
{
defaultMaterial.m_previousChangeId = defaultMaterial.m_materialInstance->GetCurrentChangeId();
defaultMaterial.m_activeMaterialAssetId = defaultMaterial.m_materialInstance->GetAssetId();
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainDefaultSurfaceMaterialChanged, GetEntityId(), defaultMaterial.m_materialInstance);
}
}
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
const bool wasPreviouslyActive = surfaceMaterialMapping.m_active;
surfaceMaterialMapping.m_active = surfaceMaterialMapping.m_materialInstance != nullptr;
anyMaterialWasAlreadyActive = anyMaterialWasAlreadyActive || wasPreviouslyActive;
anyMaterialIsActive = anyMaterialIsActive || surfaceMaterialMapping.m_active;
if (!wasPreviouslyActive && !surfaceMaterialMapping.m_active)
{
// A material has been assigned but has not yet completed loading.
}
else if (!wasPreviouslyActive && surfaceMaterialMapping.m_active)
{
// Remember the asset id so we can disconnect from the AssetBus if the material asset is removed.
surfaceMaterialMapping.m_activeMaterialAssetId = surfaceMaterialMapping.m_materialAsset.GetId();
@@ -199,27 +272,47 @@ namespace Terrain
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingCreated, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag,
surfaceMaterialMapping.m_materialInstance);
surfaceMaterialMapping.m_previousChangeId = surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId();
surfaceMaterialMapping.m_previousTag = surfaceMaterialMapping.m_surfaceTag;
}
else if (wasPreviouslyActive && !isNowActive)
else if (wasPreviouslyActive && !surfaceMaterialMapping.m_active)
{
// Don't disconnect from the AssetBus if this material is mapped more than once.
if (CountMaterialIDInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1)
if (CountMaterialIdInstances(surfaceMaterialMapping.m_activeMaterialAssetId) == 1)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(surfaceMaterialMapping.m_activeMaterialAssetId);
}
surfaceMaterialMapping.m_activeMaterialAssetId = AZ::Data::AssetId();
surfaceMaterialMapping.m_activeMaterialAssetId = {};
surfaceMaterialMapping.m_previousChangeId = AZ::RPI::Material::DEFAULT_CHANGE_ID;
surfaceMaterialMapping.m_previousTag = {};
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingDestroyed, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag);
}
else
else
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingChanged, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag,
surfaceMaterialMapping.m_materialInstance);
if (surfaceMaterialMapping.m_previousTag != surfaceMaterialMapping.m_surfaceTag)
{
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingTagChanged, GetEntityId(),
surfaceMaterialMapping.m_previousTag,
surfaceMaterialMapping.m_surfaceTag);
surfaceMaterialMapping.m_previousTag = surfaceMaterialMapping.m_surfaceTag;
}
if (surfaceMaterialMapping.m_materialInstance->GetAssetId() != surfaceMaterialMapping.m_activeMaterialAssetId ||
surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId() != surfaceMaterialMapping.m_previousChangeId)
{
surfaceMaterialMapping.m_previousChangeId = surfaceMaterialMapping.m_materialInstance->GetCurrentChangeId();
surfaceMaterialMapping.m_activeMaterialAssetId = surfaceMaterialMapping.m_materialInstance->GetAssetId();
TerrainAreaMaterialNotificationBus::Broadcast(
&TerrainAreaMaterialNotificationBus::Events::OnTerrainSurfaceMaterialMappingMaterialChanged, GetEntityId(),
surfaceMaterialMapping.m_surfaceTag,
surfaceMaterialMapping.m_materialInstance);
}
}
}
@@ -290,14 +383,28 @@ namespace Terrain
void TerrainSurfaceMaterialsListComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
// Find the missing material instance with the correct id.
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
auto checkUpdateMaterialAsset = [](TerrainSurfaceMaterialMapping& mapping, const AZ::Data::Asset<AZ::Data::AssetData>& asset) -> bool
{
if (surfaceMaterialMapping.m_materialAsset.GetId() == asset.GetId() &&
(!surfaceMaterialMapping.m_materialInstance ||
surfaceMaterialMapping.m_materialInstance->GetAssetId() != surfaceMaterialMapping.m_materialAsset.GetId()))
if (mapping.m_materialAsset.GetId() == asset.GetId() &&
(!mapping.m_materialInstance || mapping.m_materialInstance->GetAssetId() != mapping.m_materialAsset.GetId()))
{
surfaceMaterialMapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(surfaceMaterialMapping.m_materialAsset);
surfaceMaterialMapping.m_materialAsset.Release();
mapping.m_materialInstance = AZ::RPI::Material::FindOrCreate(mapping.m_materialAsset);
mapping.m_materialAsset.Release();
return true;
}
return false;
};
// First check the default material
if (!checkUpdateMaterialAsset(m_configuration.m_defaultSurfaceMaterial, asset))
{
// If the default materail wasn't updated, then check all the surface material mappings.
for (auto& surfaceMaterialMapping : m_configuration.m_surfaceMaterials)
{
if (checkUpdateMaterialAsset(surfaceMaterialMapping, asset))
{
break;
}
}
}
HandleMaterialStateChanges();
@@ -8,15 +8,18 @@
#pragma once
#include <Atom/Feature/Material/MaterialAssignment.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/EditContext.h>
#include <Atom/Feature/Material/MaterialAssignment.h>
#include <Atom/RPI.Reflect/Material/MaterialAsset.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <SurfaceData/SurfaceDataTypes.h>
#include <TerrainRenderer/TerrainAreaMaterialRequestBus.h>
namespace LmbrCentral
{
template<typename, typename>
@@ -27,16 +30,20 @@ namespace Terrain
{
struct TerrainSurfaceMaterialMapping final
{
public:
AZ_CLASS_ALLOCATOR(TerrainSurfaceMaterialMapping, AZ::SystemAllocator, 0);
AZ_RTTI(TerrainSurfaceMaterialMapping, "{37D2A586-CDDD-4FB7-A7D6-0B4CC575AB8C}");
static void Reflect(AZ::ReflectContext* context);
SurfaceData::SurfaceTag m_surfaceTag;
AZ::Data::AssetId m_activeMaterialAssetId;
AZ::Data::Asset<AZ::RPI::MaterialAsset> m_materialAsset;
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance;
AZ::Data::AssetId m_activeMaterialAssetId;
AZ::RPI::Material::ChangeId m_previousChangeId = AZ::RPI::Material::DEFAULT_CHANGE_ID;
// Surface tags not used by default material
SurfaceData::SurfaceTag m_surfaceTag;
SurfaceData::SurfaceTag m_previousTag;
bool m_active = false;
};
@@ -47,7 +54,13 @@ namespace Terrain
AZ_RTTI(TerrainSurfaceMaterialsListConfig, "{68A1CB1B-C835-4C3A-8D1C-08692E07711A}", AZ::ComponentConfig);
static void Reflect(AZ::ReflectContext* context);
TerrainSurfaceMaterialsListConfig();
TerrainSurfaceMaterialMapping m_defaultSurfaceMaterial;
AZStd::vector<TerrainSurfaceMaterialMapping> m_surfaceMaterials;
private:
static const AZ::Edit::ElementData* GetDynamicData(const void* handlerPtr, const void* elementPtr, const AZ::Uuid& elementType);
AZ::Edit::ElementData m_hideSurfaceTagData;
};
class TerrainSurfaceMaterialsListComponent
@@ -78,7 +91,7 @@ namespace Terrain
private:
void HandleMaterialStateChanges();
int CountMaterialIDInstances(AZ::Data::AssetId id) const;
int CountMaterialIdInstances(AZ::Data::AssetId id) const;
////////////////////////////////////////////////////////////////////////
// ShapeComponentNotificationsBus
@@ -45,6 +45,27 @@ namespace Terrain
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
//! The default surface material has been assigned and loaded
virtual void OnTerrainDefaultSurfaceMaterialCreated(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] AZ::Data::Instance<AZ::RPI::Material> material)
{
}
//! The default surface material has been unassigned
virtual void OnTerrainDefaultSurfaceMaterialDestroyed(
[[maybe_unused]] AZ::EntityId entityId)
{
}
//! The default surface material has been changed to a different material
virtual void OnTerrainDefaultSurfaceMaterialChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] AZ::Data::Instance<AZ::RPI::Material> newMaterial)
{
}
//! A loaded material mapped to a valid surface tag has been created
virtual void OnTerrainSurfaceMaterialMappingCreated(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag surface,
@@ -52,19 +73,30 @@ namespace Terrain
{
}
//! Either the material or surface tag was unassigned, making this mapping invalid
virtual void OnTerrainSurfaceMaterialMappingDestroyed(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag surface)
{
}
virtual void OnTerrainSurfaceMaterialMappingChanged(
//! The surface tag has changed to tag for an existing material
virtual void OnTerrainSurfaceMaterialMappingTagChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag oldSurface,
[[maybe_unused]] SurfaceData::SurfaceTag newSurface)
{
}
//! The material has changed for an existing surface tag
virtual void OnTerrainSurfaceMaterialMappingMaterialChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] SurfaceData::SurfaceTag surface,
[[maybe_unused]] AZ::Data::Instance<AZ::RPI::Material> material)
{
}
//! The bounds of this set of surface material mappings has changed
virtual void OnTerrainSurfaceMaterialMappingRegionChanged(
[[maybe_unused]] AZ::EntityId entityId,
[[maybe_unused]] const AZ::Aabb& oldRegion,
@@ -106,6 +106,8 @@ namespace Terrain
return;
}
InitializePassthroughDetailMaterial();
ClipmapBoundsDescriptor desc;
desc.m_clipmapUpdateMultiple = 1;
desc.m_clipToWorldScale = DetailTextureScale;
@@ -260,20 +262,73 @@ namespace Terrain
m_dirtyDetailRegion.AddAabb(dirtyRegion);
}
}
bool TerrainDetailMaterialManager::ForSurfaceTag(DetailMaterialListRegion& materialRegion,
SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback)
{
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
{
if (surface.m_surfaceTag == surfaceTag)
{
callback(surface);
return true;
}
}
return false;
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, MaterialInstance material)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
AZ_Error("TerrainDetailMaterialManager", materialRegion.m_defaultDetailMaterialId == InvalidDetailMaterailId,
"Default detail material created but was already set for this region.");
materialRegion.m_defaultDetailMaterialId = CreateOrUpdateDetailMaterial(material);
m_detailMaterials.GetData(materialRegion.m_defaultDetailMaterialId).refCount++;
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialDestroyed() called for region that doesn't exist.");
return;
}
CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId);
materialRegion->m_defaultDetailMaterialId = InvalidDetailMaterailId;
}
void TerrainDetailMaterialManager::OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, MaterialInstance newMaterial)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainDefaultSurfaceMaterialChanged() called for region that doesn't exist.");
return;
}
// Update existing entry or create a new material entry
uint16_t materialId = CreateOrUpdateDetailMaterial(newMaterial);
if (materialRegion->m_defaultDetailMaterialId != materialId)
{
++m_detailMaterials.GetData(materialId).refCount;
CheckDetailMaterialForDeletion(materialRegion->m_defaultDetailMaterialId);
materialRegion->m_defaultDetailMaterialId = materialId;
}
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
// Validate that the surface tag is new
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
ForSurfaceTag(materialRegion, surfaceTag, [](DetailMaterialSurface&)
{
if (surface.m_surfaceTag == surfaceTag)
{
AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag.");
return;
}
}
AZ_Error(TerrainDetailMaterialManagerName, false, "Already have a surface material mapping for this surface tag.");
});
uint16_t detailMaterialId = CreateOrUpdateDetailMaterial(material);
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, detailMaterialId });
@@ -284,52 +339,71 @@ namespace Terrain
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
[[maybe_unused]] bool found = ForSurfaceTag(materialRegion, surfaceTag,
[&](DetailMaterialSurface& surface)
{
if (surface.m_surfaceTag == surfaceTag)
{
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag)
{
AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back());
}
materialRegion.m_materialsForSurfaces.pop_back();
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
return;
if (surface.m_surfaceTag != materialRegion.m_materialsForSurfaces.back().m_surfaceTag)
{
AZStd::swap(surface, materialRegion.m_materialsForSurfaces.back());
}
materialRegion.m_materialsForSurfaces.pop_back();
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
return;
});
AZ_Error(TerrainDetailMaterialManagerName, found, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingMaterialChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
{
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for region that doesn't exist.");
return;
}
AZ_Error(TerrainDetailMaterialManagerName, false, "Could not find surface tag to destroy for OnTerrainSurfaceMaterialMappingDestroyed().");
// Update existing entry or create a new material entry
uint16_t materialId = CreateOrUpdateDetailMaterial(material);
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, surfaceTag,
[&](DetailMaterialSurface& surface)
{
if (surface.m_detailMaterialId != materialId)
{
// Updated material was a different asset than the old material, decrement ref count and
// delete if no other surface tags are using it.
++m_detailMaterials.GetData(materialId).refCount;
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
surface.m_detailMaterialId = materialId;
}
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Assert(found, "OnTerrainSurfaceMaterialMappingMaterialChanged() called for tag that doesn't exist.");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material)
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingTagChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag oldTag, SurfaceData::SurfaceTag newTag)
{
DetailMaterialListRegion& materialRegion = FindOrCreateByEntityId(entityId, m_detailMaterialRegions);
bool found = false;
uint16_t materialId = CreateOrUpdateDetailMaterial(material);
for (DetailMaterialSurface& surface : materialRegion.m_materialsForSurfaces)
DetailMaterialListRegion* materialRegion = FindByEntityId(entityId, m_detailMaterialRegions);
if (materialRegion == nullptr)
{
if (surface.m_surfaceTag == surfaceTag)
{
found = true;
if (surface.m_detailMaterialId != materialId)
{
++m_detailMaterials.GetData(materialId).refCount;
CheckDetailMaterialForDeletion(surface.m_detailMaterialId);
surface.m_detailMaterialId = materialId;
}
break;
}
AZ_Assert(false, "OnTerrainSurfaceMaterialMappingTagChanged() called for region that doesn't exist.");
return;
}
if (!found)
[[maybe_unused]] bool found = ForSurfaceTag(*materialRegion, oldTag,
[&](DetailMaterialSurface& surface)
{
++m_detailMaterials.GetData(materialId).refCount;
materialRegion.m_materialsForSurfaces.push_back({ surfaceTag, materialId });
}
m_dirtyDetailRegion.AddAabb(materialRegion.m_region);
surface.m_surfaceTag = newTag;
m_dirtyDetailRegion.AddAabb(materialRegion->m_region);
});
AZ_Assert(found, "OnTerrainSurfaceMaterialMappingTagChanged() called for tag that doesn't exist.");
}
void TerrainDetailMaterialManager::OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion)
@@ -667,23 +741,38 @@ namespace Terrain
bool isFirstMaterial = true;
float firstWeight = 0.0f;
AZ::Vector2 position(surfacePoint.m_position.GetX(), surfacePoint.m_position.GetY());
const DetailMaterialListRegion* region = FindRegionForPosition(position);
if (region == nullptr)
{
pixels.at(index).m_material1 = m_passthroughMaterialId;
++index;
return;
}
for (const auto& surfaceTagWeight : surfacePoint.m_surfaceTags)
{
if (surfaceTagWeight.m_weight > 0.0f)
{
AZ::Crc32 surfaceType = surfaceTagWeight.m_surfaceType;
uint16_t materialId = GetDetailMaterialForSurfaceTypeAndPosition(surfaceType, position);
if (materialId != m_detailMaterials.NoFreeSlot && materialId < 255)
uint16_t materialId = GetDetailMaterialForSurfaceType(*region, surfaceType);
if (materialId < 255)
{
if (isFirstMaterial)
{
// First material is valid. Save its weight to calculate blend later
pixels.at(index).m_material1 = aznumeric_cast<uint8_t>(materialId);
firstWeight = surfaceTagWeight.m_weight;
// m_blend only needs to be calculated is material 2 is found, otherwise the initial value of 0 is correct.
isFirstMaterial = false;
static constexpr float MaxValueBeforeRounding = 254.5f / 255.0f;
if (firstWeight >= MaxValueBeforeRounding)
{
break;
}
}
else
{
// Second material is valid, weight is relative based on first material's weight.
pixels.at(index).m_material2 = aznumeric_cast<uint8_t>(materialId);
float totalWeight = firstWeight + surfaceTagWeight.m_weight;
float blendWeight = 1.0f - (firstWeight / totalWeight);
@@ -691,11 +780,37 @@ namespace Terrain
break;
}
}
continue; // search for second material
}
else
{
break; // since the list is ordered, no other materials are in the list with positive weights.
// No more valid materials in list since surfaceTagWeight is ordered.
uint8_t defaultMaterial = region->m_defaultDetailMaterialId == InvalidDetailMaterailId ? m_passthroughMaterialId :
aznumeric_cast<uint8_t>(m_detailMaterials.GetData(region->m_defaultDetailMaterialId).m_detailMaterialBufferIndex);
if (isFirstMaterial)
{
// Only one material and it's the default material.
pixels.at(index).m_material1 = defaultMaterial;
}
else
{
// Second material is default, weight is exactly what the first material requested
pixels.at(index).m_material2 = defaultMaterial;
float blendWeight = 1.0f - AZStd::clamp<float>(firstWeight, 0.0f, 1.0f);
pixels.at(index).m_blend = aznumeric_cast<uint8_t>(AZStd::round(blendWeight * 255.0f));
}
}
if (pixels.at(index).m_material1 == pixels.at(index).m_material2)
{
// If the materials are the same, then make the blend 100% on the first id so the shader
// doesn't blend identical materials
pixels.at(index).m_blend = 0;
}
break;
}
++index;
};
@@ -723,23 +838,37 @@ namespace Terrain
m_detailTextureImage->UpdateImageContents(imageUpdateRequest);
}
uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position)
uint16_t TerrainDetailMaterialManager::GetDetailMaterialForSurfaceType(const DetailMaterialListRegion& materialRegion, AZ::Crc32 surfaceType) const
{
for (const auto& materialSurface : materialRegion.m_materialsForSurfaces)
{
if (materialSurface.m_surfaceTag == surfaceType)
{
return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex;
}
}
return InvalidDetailMaterailId;
}
auto TerrainDetailMaterialManager::FindRegionForPosition(const AZ::Vector2& position) const -> const DetailMaterialListRegion*
{
for (const auto& materialRegion : m_detailMaterialRegions.GetDataVector())
{
if (materialRegion.m_region.Contains(AZ::Vector3(position.GetX(), position.GetY(), 0.0f)))
{
for (const auto& materialSurface : materialRegion.m_materialsForSurfaces)
{
if (materialSurface.m_surfaceTag == surfaceType)
{
return m_detailMaterials.GetData(materialSurface.m_detailMaterialId).m_detailMaterialBufferIndex;
}
}
return &materialRegion;
}
}
return m_detailMaterials.NoFreeSlot;
return nullptr;
}
void TerrainDetailMaterialManager::InitializePassthroughDetailMaterial()
{
m_passthroughMaterialId = aznumeric_cast<uint8_t>(m_detailMaterialShaderData.Reserve());
DetailMaterialShaderData& materialShaderData = m_detailMaterialShaderData.GetElement(m_passthroughMaterialId);
// Material defaults to white (1.0, 1.0, 1.0), set the blend mode to multiply so it passes through to the macro material.
materialShaderData.m_flags = DetailTextureFlags::BlendModeMultiply;
}
auto TerrainDetailMaterialManager::FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container)
@@ -784,5 +913,5 @@ namespace Terrain
}
AZ_Assert(false, "Entity Id not found in container.")
}
}
@@ -11,6 +11,7 @@
#include <AzCore/base.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/function/function_template.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
@@ -151,7 +152,11 @@ namespace Terrain
AZ::EntityId m_entityId;
AZ::Aabb m_region{AZ::Aabb::CreateNull()};
AZStd::vector<DetailMaterialSurface> m_materialsForSurfaces;
uint16_t m_defaultDetailMaterialId;
};
using DetailMaterialContainer = AZ::Render::IndexedDataVector<DetailMaterialData>;
static constexpr auto InvalidDetailMaterailId = DetailMaterialContainer::NoFreeSlot;
// System-level parameters
static constexpr int32_t DetailTextureSize{ 1024 };
@@ -162,9 +167,14 @@ namespace Terrain
void OnTerrainDataChanged(const AZ::Aabb& dirtyRegion, TerrainDataChangedMask dataChangedMask) override;
// TerrainAreaMaterialNotificationBus overrides...
void OnTerrainDefaultSurfaceMaterialCreated(AZ::EntityId entityId, AZ::Data::Instance<AZ::RPI::Material> material) override;
void OnTerrainDefaultSurfaceMaterialDestroyed(AZ::EntityId entityId) override;
void OnTerrainDefaultSurfaceMaterialChanged(AZ::EntityId entityId, AZ::Data::Instance<AZ::RPI::Material> newMaterial) override;
void OnTerrainSurfaceMaterialMappingCreated(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingDestroyed(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag) override;
void OnTerrainSurfaceMaterialMappingChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingMaterialChanged(AZ::EntityId entityId, SurfaceData::SurfaceTag surfaceTag, MaterialInstance material) override;
void OnTerrainSurfaceMaterialMappingTagChanged(
AZ::EntityId entityId, SurfaceData::SurfaceTag oldSurfaceTag, SurfaceData::SurfaceTag newSurfaceTag) override;
void OnTerrainSurfaceMaterialMappingRegionChanged(AZ::EntityId entityId, const AZ::Aabb& oldRegion, const AZ::Aabb& newRegion) override;
//! Removes all images from all detail materials from the bindless image array
@@ -186,22 +196,32 @@ namespace Terrain
//! Updates the detail texture in a given area
void UpdateDetailTexture(const AZ::Aabb& worldUpdateAabb, const Aabb2i& textureUpdateAabb);
//! Finds the detail material Id for a surface type and position
uint16_t GetDetailMaterialForSurfaceTypeAndPosition(AZ::Crc32 surfaceType, const AZ::Vector2& position);
//! Finds the detail material Id for a region and surface type
uint16_t GetDetailMaterialForSurfaceType(const DetailMaterialListRegion& materialRegion, AZ::Crc32 surfaceType) const;
//! Finds a region for a position. Returns nullptr if none found.
const DetailMaterialListRegion* FindRegionForPosition(const AZ::Vector2& position) const;
//! Initializes shader data for the default passthrough material which is used when no other detail material is found.
void InitializePassthroughDetailMaterial();
using DefaultMaterialSurfaceCallback = AZStd::function<void(DetailMaterialSurface&)>;
bool ForSurfaceTag(DetailMaterialListRegion& materialRegion,
SurfaceData::SurfaceTag surfaceTag, DefaultMaterialSurfaceCallback callback);
DetailMaterialListRegion* FindByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
DetailMaterialListRegion& FindOrCreateByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
void RemoveByEntityId(AZ::EntityId entityId, AZ::Render::IndexedDataVector<DetailMaterialListRegion>& container);
AZStd::shared_ptr<AZ::Render::BindlessImageArrayHandler> m_bindlessImageHandler;
AZ::Data::Instance<AZ::RPI::AttachmentImage> m_detailTextureImage;
AZ::Render::IndexedDataVector<DetailMaterialData> m_detailMaterials;
DetailMaterialContainer m_detailMaterials;
AZ::Render::IndexedDataVector<DetailMaterialListRegion> m_detailMaterialRegions;
AZ::Render::SparseVector<DetailMaterialShaderData> m_detailMaterialShaderData;
AZ::Render::GpuBufferHandler m_detailMaterialDataBuffer;
uint8_t m_passthroughMaterialId = 0;
AZ::Aabb m_dirtyDetailRegion{ AZ::Aabb::CreateNull() };
ClipmapBounds m_detailMaterialIdBounds;
@@ -212,6 +232,6 @@ namespace Terrain
bool m_isInitialized{ false };
bool m_detailMaterialBufferNeedsUpdate{ false };
bool m_detailImageNeedsUpdate{ false };
};
}
@@ -177,6 +177,193 @@ bool TerrainSystem::InWorldBounds(float x, float y) const
return false;
}
// Generate positions to be queried based on the sampler type.
void TerrainSystem::GenerateQueryPositions(const AZStd::span<AZ::Vector3>& inPositions,
AZStd::vector<AZ::Vector3>& outPositions,
Sampler sampler) const
{
const float minHeight = m_currentSettings.m_worldBounds.GetMin().GetZ();
for (auto& position : inPositions)
{
switch(sampler)
{
case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR:
{
AZ::Vector2 normalizedDelta;
AZ::Vector2 pos0;
ClampPosition(position.GetX(), position.GetY(), pos0, normalizedDelta);
const AZ::Vector2 pos1(pos0.GetX() + m_currentSettings.m_heightQueryResolution,
pos0.GetY() + m_currentSettings.m_heightQueryResolution);
outPositions.emplace_back(AZ::Vector3(pos0.GetX(), pos0.GetY(), minHeight));
outPositions.emplace_back(AZ::Vector3(pos1.GetX(), pos0.GetY(), minHeight));
outPositions.emplace_back(AZ::Vector3(pos0.GetX(), pos1.GetY(), minHeight));
outPositions.emplace_back(AZ::Vector3(pos1.GetX(), pos1.GetY(), minHeight));
}
break;
case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP:
{
AZ::Vector2 normalizedDelta;
AZ::Vector2 clampedPosition;
ClampPosition(position.GetX(), position.GetY(), clampedPosition, normalizedDelta);
outPositions.emplace_back(AZ::Vector3(clampedPosition.GetX(), clampedPosition.GetY(), minHeight));
}
break;
case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT:
[[fallthrough]];
default:
outPositions.emplace_back(AZ::Vector3(position.GetX(), position.GetY(), minHeight));
break;
}
}
}
AZStd::vector<AZ::Vector3> TerrainSystem::GenerateInputPositionsFromRegion(
const AZ::Aabb& inRegion,
const AZ::Vector2& stepSize) const
{
AZStd::vector<AZ::Vector3> inPositions;
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
inPositions.reserve(numSamplesX * numSamplesY);
for (size_t y = 0; y < numSamplesY; y++)
{
float fy = aznumeric_cast<float>(inRegion.GetMin().GetY() + (y * stepSize.GetY()));
for (size_t x = 0; x < numSamplesX; x++)
{
float fx = aznumeric_cast<float>(inRegion.GetMin().GetX() + (x * stepSize.GetX()));
inPositions.emplace_back(AZ::Vector3(fx, fy, 0.0f));
}
}
return inPositions;
}
void TerrainSystem::MakeBulkQueries(
const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
BulkQueriesCallback queryCallback) const
{
AZ::Aabb bounds;
AZ::EntityId prevAreaId = FindBestAreaEntityAtPosition(inPositions[0].GetX(), inPositions[0].GetY(), bounds);
// We use a sliding window here and update the window end for each
// position that falls in the same area as the previous positions. This consumes lesser memory
// than sorting the points into separate lists and handling putting them back together.
// This may be sub optimal if the points are randomly distributed in the list as opposed
// to points in the same area id being close to each other.
size_t windowStart = 0;
size_t windowEnd = 0;
const size_t numPositions = inPositions.size();
for(int i = 1; i < numPositions; i++)
{
AZ::EntityId areaId = FindBestAreaEntityAtPosition(inPositions[i].GetX(), inPositions[i].GetY(), bounds);
bool queryHeights = false;
if (areaId == prevAreaId)
{
// Update window end to current position.
// If it's the last position, submit the query.
windowEnd = i;
if (windowEnd == numPositions - 1)
{
queryHeights = true;
}
}
else
{
queryHeights = true;
}
if (queryHeights)
{
// If the area id is a default entity id, it usually means the
// position is outside world bounds.
if (prevAreaId != AZ::EntityId())
{
size_t spanLength = (windowEnd - windowStart) + 1;
queryCallback(AZStd::span<AZ::Vector3>(inPositions.begin() + windowStart, spanLength),
AZStd::span<AZ::Vector3>(outPositions.begin() + windowStart, spanLength),
AZStd::span<bool>(outTerrainExists.begin() + windowStart, spanLength),
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList>(outSurfaceWeights.begin() + windowStart, spanLength),
prevAreaId);
}
// Reset the window to start at the current position. Set the new area
// id on which to run the next query.
windowStart = windowEnd = i;
prevAreaId = areaId;
}
}
}
void TerrainSystem::GetHeightsSynchronous(const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<float> heights, AZStd::span<bool> terrainExists) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
AZStd::vector<AZ::Vector3> outPositions;
AZStd::vector<bool> outTerrainExists;
// outPositions holds the iterators to results of the bulk queries.
// In the case of the bilinear sampler, we'll be making 4 queries per
// input position.
size_t indexStepSize = (sampler == AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR) ? 4 : 1;
outPositions.reserve(inPositions.size() * indexStepSize);
outTerrainExists.resize(inPositions.size() * indexStepSize);
GenerateQueryPositions(inPositions, outPositions, sampler);
auto callback = []([[maybe_unused]] const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
[[maybe_unused]] AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
AZ::EntityId areaId)
{
AZ_Assert((inPositions.size() == outPositions.size() && inPositions.size() == outTerrainExists.size()),
"The sizes of the terrain exists list and in/out positions list should match.");
Terrain::TerrainAreaHeightRequestBus::Event(areaId, &Terrain::TerrainAreaHeightRequestBus::Events::GetHeights,
outPositions, outTerrainExists);
};
// This will be unused for heights. It's fine if it's empty.
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights;
MakeBulkQueries(outPositions, outPositions, outTerrainExists, outSurfaceWeights, callback);
// Compute/store the final result
for (size_t i = 0, iteratorIndex = 0; i < inPositions.size(); i++, iteratorIndex += indexStepSize)
{
switch(sampler)
{
case AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR:
{
// We now need to compute the final height after all the bulk queries are done.
AZ::Vector2 normalizedDelta;
AZ::Vector2 clampedPosition;
ClampPosition(inPositions[i].GetX(), inPositions[i].GetY(), clampedPosition, normalizedDelta);
const float heightX0Y0 = outPositions[iteratorIndex].GetZ();
const float heightX1Y0 = outPositions[iteratorIndex + 1].GetZ();
const float heightX0Y1 = outPositions[iteratorIndex + 2].GetZ();
const float heightX1Y1 = outPositions[iteratorIndex + 3].GetZ();
const float heightXY0 = AZ::Lerp(heightX0Y0, heightX1Y0, normalizedDelta.GetX());
const float heightXY1 = AZ::Lerp(heightX0Y1, heightX1Y1, normalizedDelta.GetX());
heights[i] = AZ::Lerp(heightXY0, heightXY1, normalizedDelta.GetY());
terrainExists[i] = outTerrainExists[iteratorIndex];
}
break;
case AzFramework::Terrain::TerrainDataRequests::Sampler::CLAMP:
[[fallthrough]];
case AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT:
[[fallthrough]];
default:
// For clamp and exact, we just need to store the results of the bulk query.
heights[i] = outPositions[iteratorIndex].GetZ();
terrainExists[i] = outTerrainExists[iteratorIndex];
break;
}
}
}
float TerrainSystem::GetHeightSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const
{
bool terrainExists = false;
@@ -315,6 +502,39 @@ bool TerrainSystem::GetIsHoleFromFloats(float x, float y, Sampler sampler) const
return !terrainExists;
}
void TerrainSystem::GetNormalsSynchronous(const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<AZ::Vector3> normals, AZStd::span<bool> terrainExists) const
{
AZStd::vector<AZ::Vector3> directionVectors;
directionVectors.reserve(inPositions.size() * 4);
const AZ::Vector2 range(m_currentSettings.m_heightQueryResolution / 2.0f, m_currentSettings.m_heightQueryResolution / 2.0f);
size_t indexStepSize = 4;
for (auto& position : inPositions)
{
directionVectors.emplace_back(position.GetX(), position.GetY() - range.GetY(), 0.0f);
directionVectors.emplace_back(position.GetX() - range.GetX(), position.GetY(), 0.0f);
directionVectors.emplace_back(position.GetX() + range.GetX(), position.GetY(), 0.0f);
directionVectors.emplace_back(position.GetX(), position.GetY() + range.GetY(), 0.0f);
}
AZStd::vector<float> heights(directionVectors.size());
AZStd::vector<bool> exists(directionVectors.size());
GetHeightsSynchronous(directionVectors, sampler, heights, exists);
for (size_t i = 0, iteratorIndex = 0; i < inPositions.size(); i++, iteratorIndex += indexStepSize)
{
directionVectors[iteratorIndex].SetZ(heights[iteratorIndex]);
directionVectors[iteratorIndex + 1].SetZ(heights[iteratorIndex + 1]);
directionVectors[iteratorIndex + 2].SetZ(heights[iteratorIndex + 2]);
directionVectors[iteratorIndex + 3].SetZ(heights[iteratorIndex + 3]);
normals[i] = (directionVectors[iteratorIndex + 2] - directionVectors[iteratorIndex + 1]).
Cross(directionVectors[iteratorIndex + 3] - directionVectors[iteratorIndex]).GetNormalized();
terrainExists[i] = exists[iteratorIndex];
}
}
AZ::Vector3 TerrainSystem::GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
@@ -471,6 +691,35 @@ AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::A
return AZ::EntityId();
}
void TerrainSystem::GetOrderedSurfaceWeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions,
[[maybe_unused]] Sampler sampler,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList,
AZStd::span<bool> terrainExists) const
{
if (terrainExists.size() == outSurfaceWeightsList.size())
{
AZStd::vector<float> heights(inPositions.size());
GetHeightsSynchronous(inPositions, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, heights, terrainExists);
}
auto callback = [](const AZStd::span<AZ::Vector3> inPositions,
[[maybe_unused]] AZStd::span<AZ::Vector3> outPositions,
[[maybe_unused]] AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
AZ::EntityId areaId)
{
AZ_Assert(inPositions.size() == outSurfaceWeights.size(),
"The sizes of the surface weights list and in/out positions list should match.");
Terrain::TerrainAreaSurfaceRequestBus::Event(areaId, &Terrain::TerrainAreaSurfaceRequestBus::Events::GetSurfaceWeightsFromList,
inPositions, outSurfaceWeights);
};
// This will be unused for surface weights. It's fine if it's empty.
AZStd::vector<AZ::Vector3> outPositions;
MakeBulkQueries(inPositions, outPositions, terrainExists, outSurfaceWeightsList, callback);
}
void TerrainSystem::GetOrderedSurfaceWeights(
const float x,
const float y,
@@ -551,13 +800,17 @@ void TerrainSystem::ProcessHeightsFromList(
return;
}
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<float> heights(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
surfacePoint.m_position.SetZ(GetHeight(position, sampleFilter, &terrainExists));
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position = inPositions[i];
surfacePoint.m_position.SetZ(heights[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@@ -571,13 +824,17 @@ void TerrainSystem::ProcessNormalsFromList(
return;
}
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
surfacePoint.m_normal = GetNormal(position, sampleFilter, &terrainExists);
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position = inPositions[i];
surfacePoint.m_normal = AZStd::move(normals[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@@ -591,13 +848,17 @@ void TerrainSystem::ProcessSurfaceWeightsFromList(
return;
}
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
GetSurfaceWeights(position, surfacePoint.m_surfaceTags, sampleFilter, &terrainExists);
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position = inPositions[i];
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@@ -611,13 +872,26 @@ void TerrainSystem::ProcessSurfacePointsFromList(
return;
}
AZStd::vector<float> heights(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
// We can skip the unnecessary call to GetHeights since we already
// got the terrain exists flags in the earlier call to GetHeights
AZStd::vector<bool> terrainExistsEmpty;
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExistsEmpty);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (const auto& position : inPositions)
for (size_t i = 0; i < inPositions.size(); i++)
{
bool terrainExists = false;
surfacePoint.m_position = position;
GetSurfacePoint(position, surfacePoint, sampleFilter, &terrainExists);
perPositionCallback(surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]);
surfacePoint.m_normal = AZStd::move(normals[i]);
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(surfacePoint, terrainExists[i]);
}
}
@@ -723,20 +997,23 @@ void TerrainSystem::ProcessHeightsFromRegion(
return;
}
const size_t numSamplesX = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetX() / stepSize.GetX()));
const size_t numSamplesY = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetY() / stepSize.GetY()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<float> heights(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 0; y < numSamplesY; y++)
{
float fy = aznumeric_cast<float>(inRegion.GetMin().GetY() + (y * stepSize.GetY()));
for (size_t x = 0; x < numSamplesX; x++)
{
bool terrainExists = false;
float fx = aznumeric_cast<float>(inRegion.GetMin().GetX() + (x * stepSize.GetX()));
surfacePoint.m_position.Set(fx, fy, 0.0f);
surfacePoint.m_position.SetZ(GetHeight(surfacePoint.m_position, sampleFilter, &terrainExists));
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}
@@ -753,20 +1030,24 @@ void TerrainSystem::ProcessNormalsFromRegion(
return;
}
const size_t numSamplesX = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetX() / stepSize.GetX()));
const size_t numSamplesY = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetY() / stepSize.GetY()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<bool> terrainExists(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 0; y < numSamplesY; y++)
{
float fy = aznumeric_cast<float>(inRegion.GetMin().GetY() + (y * stepSize.GetY()));
for (size_t x = 0; x < numSamplesX; x++)
{
bool terrainExists = false;
float fx = aznumeric_cast<float>(inRegion.GetMin().GetX() + (x * stepSize.GetX()));
surfacePoint.m_position.Set(fx, fy, 0.0f);
surfacePoint.m_normal = GetNormal(surfacePoint.m_position, sampleFilter, &terrainExists);
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), 0.0f);
surfacePoint.m_normal = AZStd::move(normals[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}
@@ -783,20 +1064,24 @@ void TerrainSystem::ProcessSurfaceWeightsFromRegion(
return;
}
const size_t numSamplesX = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetX() / stepSize.GetX()));
const size_t numSamplesY = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetY() / stepSize.GetY()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExists);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 0; y < numSamplesY; y++)
{
float fy = aznumeric_cast<float>(inRegion.GetMin().GetY() + (y * stepSize.GetY()));
for (size_t x = 0; x < numSamplesX; x++)
{
bool terrainExists = false;
float fx = aznumeric_cast<float>(inRegion.GetMin().GetX() + (x * stepSize.GetX()));
surfacePoint.m_position.Set(fx, fy, 0.0f);
GetSurfaceWeights(surfacePoint.m_position, surfacePoint.m_surfaceTags, sampleFilter, &terrainExists);
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), 0.0f);
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}
@@ -813,20 +1098,33 @@ void TerrainSystem::ProcessSurfacePointsFromRegion(
return;
}
const size_t numSamplesX = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetX() / stepSize.GetX()));
const size_t numSamplesY = aznumeric_cast<size_t>(ceil(inRegion.GetExtents().GetY() / stepSize.GetY()));
const auto [numSamplesX, numSamplesY] = GetNumSamplesFromRegion(inRegion, stepSize);
AZStd::vector<AZ::Vector3> inPositions = GenerateInputPositionsFromRegion(inRegion, stepSize);
AZStd::vector<float> heights(inPositions.size());
AZStd::vector<AZ::Vector3> normals(inPositions.size());
AZStd::vector<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList(inPositions.size());
AZStd::vector<bool> terrainExists(inPositions.size());
GetHeightsSynchronous(inPositions, sampleFilter, heights, terrainExists);
GetNormalsSynchronous(inPositions, sampleFilter, normals, terrainExists);
// We can skip the unnecessary call to GetHeights since we already
// got the terrain exists flags in the earlier call to GetHeights
AZStd::vector<bool> terrainExistsEmpty;
GetOrderedSurfaceWeightsFromList(inPositions, sampleFilter, outSurfaceWeightsList, terrainExistsEmpty);
AzFramework::SurfaceData::SurfacePoint surfacePoint;
for (size_t y = 0; y < numSamplesY; y++)
for (size_t y = 0, i = 0; y < numSamplesY; y++)
{
float fy = aznumeric_cast<float>(inRegion.GetMin().GetY() + (y * stepSize.GetY()));
for (size_t x = 0; x < numSamplesX; x++)
{
bool terrainExists = false;
float fx = aznumeric_cast<float>(inRegion.GetMin().GetX() + (x * stepSize.GetX()));
surfacePoint.m_position.Set(fx, fy, 0.0f);
GetSurfacePoint(surfacePoint.m_position, surfacePoint, sampleFilter, &terrainExists);
perPositionCallback(x, y, surfacePoint, terrainExists);
surfacePoint.m_position.Set(inPositions[i].GetX(), inPositions[i].GetY(), heights[i]);
surfacePoint.m_normal = AZStd::move(normals[i]);
surfacePoint.m_surfaceTags = AZStd::move(outSurfaceWeightsList[i]);
perPositionCallback(x, y, surfacePoint, terrainExists[i]);
i++;
}
}
}
@@ -207,6 +207,38 @@ namespace Terrain
float GetTerrainAreaHeight(float x, float y, bool& terrainExists) const;
AZ::Vector3 GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const;
typedef AZStd::function<void(
const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
AZ::EntityId areaId)> BulkQueriesCallback;
void GetHeightsSynchronous(
const AZStd::span<AZ::Vector3>& inPositions,
Sampler sampler, AZStd::span<float> heights,
AZStd::span<bool> terrainExists) const;
void GetNormalsSynchronous(
const AZStd::span<AZ::Vector3>& inPositions,
Sampler sampler, AZStd::span<AZ::Vector3> normals,
AZStd::span<bool> terrainExists) const;
void GetOrderedSurfaceWeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList,
AZStd::span<bool> terrainExists) const;
void MakeBulkQueries(
const AZStd::span<AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWieghts,
BulkQueriesCallback queryCallback) const;
void GenerateQueryPositions(const AZStd::span<AZ::Vector3>& inPositions,
AZStd::vector<AZ::Vector3>& outPositions,
Sampler sampler) const;
AZStd::vector<AZ::Vector3> GenerateInputPositionsFromRegion(
const AZ::Aabb& inRegion,
const AZ::Vector2& stepSize) const;
// AZ::TickBus::Handler overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+25 -3
View File
@@ -158,6 +158,15 @@ namespace UnitTest
// Let the test function modify these values based on the needs of the specific test.
mockHeights(outPosition, terrainExists);
});
ON_CALL(*m_terrainAreaHeightRequests, GetHeights)
.WillByDefault(
[mockHeights](AZStd::span<AZ::Vector3> inOutPositionList, AZStd::span<bool> terrainExistsList)
{
for (int i = 0; i < inOutPositionList.size(); i++)
{
mockHeights(inOutPositionList[i], terrainExistsList[i]);
}
});
ActivateEntity(entity.get());
return entity;
@@ -184,9 +193,9 @@ namespace UnitTest
tagWeight3.m_weight = 0.3f;
expectedTags.push_back(tagWeight3);
m_terrainAreaSurfaceRequests = AZStd::make_unique<NiceMock<UnitTest::MockTerrainAreaSurfaceRequestBus>>(entity->GetId());
ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeights).WillByDefault(
[tagWeight1, tagWeight2, tagWeight3](const AZ::Vector3& position, AzFramework::SurfaceData::SurfaceTagWeightList& surfaceWeights)
auto mockGetSurfaceWeights = [tagWeight1, tagWeight2, tagWeight3](
const AZ::Vector3& position,
AzFramework::SurfaceData::SurfaceTagWeightList& surfaceWeights)
{
surfaceWeights.clear();
float absYPos = fabsf(position.GetY());
@@ -202,6 +211,19 @@ namespace UnitTest
{
surfaceWeights.push_back(tagWeight3);
}
};
m_terrainAreaSurfaceRequests = AZStd::make_unique<NiceMock<UnitTest::MockTerrainAreaSurfaceRequestBus>>(entity->GetId());
ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeights).WillByDefault(mockGetSurfaceWeights);
ON_CALL(*m_terrainAreaSurfaceRequests, GetSurfaceWeightsFromList).WillByDefault(
[mockGetSurfaceWeights](
AZStd::span<const AZ::Vector3> inPositionList,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList)
{
for (size_t i = 0; i < inPositionList.size(); i++)
{
mockGetSurfaceWeights(inPositionList[i], outSurfaceWeightsList[i]);
}
}
);
}
@@ -25,6 +25,8 @@ set(FILES
Source/EditorComponents/EditorTerrainSystemComponent.h
Source/EditorTerrainModule.cpp
Source/EditorTerrainModule.h
Source/EditorSurfaceTagListProvider.h
Source/EditorSurfaceTagListProvider.cpp
Source/TerrainModule.cpp
Source/TerrainModule.h
Source/TerrainRenderer/EditorComponents/EditorTerrainSurfaceMaterialsListComponent.cpp
@@ -175,13 +175,20 @@ class AbstractResourceLocator(object):
return os.path.join(self.build_directory(), 'AssetProcessor')
def asset_processor_batch(self):
""""
"""
Return path for the AssetProcessorBatch compatible with this build platform and configuration
ex. engine_root/dev/mac/bin/profile/AssetProcessorBatch
:return: path to AssetProcessorBatch
"""
return os.path.join(self.build_directory(), 'AssetProcessorBatch')
def ap_job_logs(self):
"""
Return path to the Asset Processor JobLogs directory.
:return: path to <project>/user/log/JobLogs
"""
return os.path.join(self.project_log(), 'JobLogs')
def editor(self):
"""
Return path to the editor executable compatible with the current build.
@@ -851,11 +851,11 @@ class EditorTestSuite():
workspace.artifact_manager.save_artifact(os.path.join(editor_utils.retrieve_log_path(run_id, workspace), log_name),
f'({run_id}){log_name}')
if return_code == 0:
# No need to scrap the output, as all the tests have passed
# No need to scrape the output, as all the tests have passed
for test_spec in test_spec_list:
results[test_spec.__name__] = Result.Pass.create(test_spec, output, editor_log_content)
else:
# Scrap the output to attempt to find out which tests failed.
# Scrape the output to attempt to find out which tests failed.
# This function should always populate the result list, if it didn't find it, it will have "Unknown" type of result
results = self._get_results_using_output(test_spec_list, output, editor_log_content)
assert len(results) == len(test_spec_list), "bug in _get_results_using_output(), the number of results don't match the tests ran"
@@ -940,6 +940,9 @@ class EditorTestSuite():
editor_test_data.results.update(results)
test_name, test_result = next(iter(results.items()))
self._report_result(test_name, test_result)
# If test did not pass, save assets with errors and warnings
if not isinstance(test_result, Result.Pass):
editor_utils.save_failed_asset_joblogs(workspace)
def _run_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData,
test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None:
@@ -961,6 +964,11 @@ class EditorTestSuite():
extra_cmdline_args)
assert results is not None
editor_test_data.results.update(results)
# If at least one test did not pass, save assets with errors and warnings
for result in results:
if not isinstance(result, Result.Pass):
editor_utils.save_failed_asset_joblogs(workspace)
return
def _run_parallel_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData,
test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None:
@@ -1007,8 +1015,14 @@ class EditorTestSuite():
for t in threads:
t.join()
save_asset_logs = False
for result in results_per_thread:
editor_test_data.results.update(result)
if not isinstance(result, Result.Pass):
save_asset_logs = True
# If at least one test did not pass, save assets with errors and warnings
if save_asset_logs:
editor_utils.save_failed_asset_joblogs(workspace)
def _run_parallel_batched_tests(self, request: Request, workspace: AbstractWorkspace, editor: Editor, editor_test_data: TestData,
test_spec_list: list[EditorSharedTest], extra_cmdline_args: list[str] = []) -> None:
@@ -1056,8 +1070,14 @@ class EditorTestSuite():
for t in threads:
t.join()
save_asset_logs = False
for result in results_per_thread:
editor_test_data.results.update(result)
if not isinstance(result, Result.Pass):
save_asset_logs = True
# If at least one test did not pass, save assets with errors and warnings
if save_asset_logs:
editor_utils.save_failed_asset_joblogs(workspace)
def _get_number_parallel_editors(self, request: Request) -> int:
"""
@@ -10,6 +10,7 @@ from __future__ import annotations
import os
import time
import logging
import re
import ly_test_tools.environment.process_utils as process_utils
import ly_test_tools.environment.waiter as waiter
@@ -151,3 +152,41 @@ def retrieve_last_run_test_index_from_output(test_spec_list: list[EditorTestBase
else:
index += 1
return index
def save_failed_asset_joblogs(workspace: AbstractWorkspace) -> None:
"""
Checks all asset logs in the JobLogs directory to see if the asset has any warnings or errors. If so, the asset is
saved via ArtifactManager.
:param workspace: The AbstractWorkspace to access the JobLogs path
:return: None
"""
for walk_tuple in os.walk(workspace.paths.ap_job_logs()):
for log_file in walk_tuple[2]:
full_log_path = os.path.join(walk_tuple[0], log_file)
# Only save asset logs that contain errors or warnings
if _check_log_errors_warnings(full_log_path):
try:
workspace.artifact_manager.save_artifact(full_log_path)
except Exception as e: # Purposefully broad
logger.warning(f"Error when saving log at path:{full_log_path}\n{e}")
def _check_log_errors_warnings(log_path: str) -> bool:
"""
Checks to see if the asset log contains any errors or warnings. Also returns True is no regex is found because
something probably went wrong.
Example log lines: ~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 0 errors, 1 warnings
:param log_path: The full path to the asset log file to read
:return: True if the regex finds an error or warning, else False
"""
log_regex = "(\\d+) errors, (\\d+) warnings"
with open(log_path, 'r') as opened_asset_log:
for log_line in opened_asset_log:
regex_match = re.search(log_regex, log_line)
if regex_match is not None:
break
# If we match any non zero numbers in: n error, n warnings
if regex_match is None or (int)(regex_match.group(1)) != 0 or (int)(regex_match.group(2)) != 0:
return True
return False
@@ -170,3 +170,59 @@ class TestEditorTestUtils(unittest.TestCase):
mock_test_list.append(mock_test)
assert 0 == editor_test_utils.retrieve_last_run_test_index_from_output(mock_test_list, mock_editor_output)
@mock.patch('ly_test_tools.o3de.editor_test_utils._check_log_errors_warnings')
@mock.patch('os.walk')
def test_SaveFailedAssetJoblogs_ManyValidLogs_SavesCorrectly(self, mock_walk, mock_check_log):
mock_workspace = mock.MagicMock()
mock_walk.return_value = [['MockDirectory', None, ['mock_log.log']],
['MockDirectory2', None, ['mock_log2.log']]]
mock_check_log.return_value = True
editor_test_utils.save_failed_asset_joblogs(mock_workspace)
assert mock_workspace.artifact_manager.save_artifact.call_count == 2
@mock.patch('ly_test_tools.o3de.editor_test_utils._check_log_errors_warnings')
@mock.patch('os.walk')
def test_SaveFailedAssetJoblogs_ManyInvalidLogs_NoSaves(self, mock_walk, mock_check_log):
mock_workspace = mock.MagicMock()
mock_walk.return_value = [['MockDirectory', None, ['mock_log.log']],
['MockDirectory2', None, ['mock_log2.log']]]
mock_check_log.return_value = False
editor_test_utils.save_failed_asset_joblogs(mock_workspace)
assert mock_workspace.artifact_manager.save_artifact.call_count == 0
def test_CheckLogErrorWarnings_ValidLine_ReturnsTrue(self):
mock_log = '~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 0 errors, 1 warnings'
mock_log_path = mock.MagicMock()
with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file:
expected = editor_test_utils._check_log_errors_warnings(mock_log_path)
assert expected
def test_CheckLogErrorWarnings_MultipleValidLine_ReturnsTrue(self):
mock_log = 'foo\nfoo\n~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 1 errors, 1 warnings'
mock_log_path = mock.MagicMock()
with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file:
expected = editor_test_utils._check_log_errors_warnings(mock_log_path)
assert expected
def test_CheckLogErrorWarnings_InvalidLine_ReturnsFalse(self):
mock_log = 'foo\n~~1643759303647~~1~~00000000000009E0~~AssetBuilder~~S: 0 errors, 0 warnings'
mock_log_path = mock.MagicMock()
with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file:
expected = editor_test_utils._check_log_errors_warnings(mock_log_path)
assert not expected
def test_CheckLogErrorWarnings_InvalidRegex_ReturnsTrue(self):
mock_log = 'Invalid last line'
mock_log_path = mock.MagicMock()
with mock.patch('builtins.open', mock.mock_open(read_data=mock_log)) as mock_file:
expected = editor_test_utils._check_log_errors_warnings(mock_log_path)
assert expected
@@ -871,6 +871,7 @@ class TestRunningTests(unittest.TestCase):
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._report_result')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._exec_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock())
def test_RunSingleTest_ValidTest_ReportsResults(self, mock_setup_test, mock_exec_editor_test, mock_report_result):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_test_data = mock.MagicMock()
@@ -903,6 +904,7 @@ class TestRunningTests(unittest.TestCase):
@mock.patch('threading.Thread')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock())
def test_RunParallelTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors, mock_thread):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_get_num_editors.return_value = 2
@@ -919,6 +921,7 @@ class TestRunningTests(unittest.TestCase):
@mock.patch('threading.Thread')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock())
def test_RunParallelTests_TenTestsAndTwoEditors_TenThreads(self, mock_setup_test, mock_get_num_editors, mock_thread):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
mock_get_num_editors.return_value = 2
@@ -937,6 +940,7 @@ class TestRunningTests(unittest.TestCase):
@mock.patch('threading.Thread')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock())
def test_RunParallelTests_TenTestsAndThreeEditors_TenThreads(self, mock_setup_test, mock_get_num_editors,
mock_thread):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
@@ -956,6 +960,7 @@ class TestRunningTests(unittest.TestCase):
@mock.patch('threading.Thread')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock())
def test_RunParallelBatchedTests_TwoTestsAndEditors_TwoThreads(self, mock_setup_test, mock_get_num_editors,
mock_thread):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
@@ -973,6 +978,7 @@ class TestRunningTests(unittest.TestCase):
@mock.patch('threading.Thread')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock())
def test_RunParallelBatchedTests_TenTestsAndTwoEditors_2Threads(self, mock_setup_test, mock_get_num_editors,
mock_thread):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
@@ -992,6 +998,7 @@ class TestRunningTests(unittest.TestCase):
@mock.patch('threading.Thread')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._get_number_parallel_editors')
@mock.patch('ly_test_tools.o3de.editor_test.EditorTestSuite._setup_editor_test')
@mock.patch('ly_test_tools.o3de.editor_test_utils.save_failed_asset_joblogs', mock.MagicMock())
def test_RunParallelBatchedTests_TenTestsAndThreeEditors_ThreeThreads(self, mock_setup_test, mock_get_num_editors,
mock_thread):
mock_test_suite = ly_test_tools.o3de.editor_test.EditorTestSuite()
+1 -1
View File
@@ -42,5 +42,5 @@ ly_associate_package(PACKAGE_NAME astc-encoder-3.2-rev5-mac
ly_associate_package(PACKAGE_NAME ISPCTexComp-36b80aa-rev1-mac TARGETS ISPCTexComp PACKAGE_HASH 8a4e93277b8face6ea2fd57c6d017bdb55643ed3d6387110bc5f6b3b884dd169)
ly_associate_package(PACKAGE_NAME lz4-1.9.3-vcpkg-rev4-mac TARGETS lz4 PACKAGE_HASH 891ff630bf34f7ab1d8eaee2ea0a8f1fca89dbdc63fca41ee592703dd488a73b)
ly_associate_package(PACKAGE_NAME azslc-1.7.35-rev1-mac TARGETS azslc PACKAGE_HASH 03cb1ea8c47d4c80c893e2e88767272d5d377838f5ba94b777a45902dd85052e)
ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev1-mac TARGETS SQLite PACKAGE_HASH f9101023f99cf32fc5867284ceb28c0761c23d2c5a4b1748349c69f976a2fbea)
ly_associate_package(PACKAGE_NAME SQLite-3.37.2-rev2-mac TARGETS SQLite PACKAGE_HASH b7d9abdb68045003e030e1a9a805db1aefa5e8fde6dccfbb4fab3a06249a41fc)
ly_associate_package(PACKAGE_NAME AwsIotDeviceSdkCpp-1.15.2-rev2-mac TARGETS AwsIotDeviceSdkCpp PACKAGE_HASH 4854edb7b88fa6437b4e69e87d0ee111a25313ac2a2db5bb2f8b674ba0974f95)
+60 -45
View File
@@ -6,13 +6,13 @@ attrs==20.1.0 \
--hash=sha256:0ef97238856430dcf9228e07f316aefc17e8939fc8507e18c6501b761ef1a42a \
--hash=sha256:2867b7b9f8326499ab5b0e2d12801fa5c98842d2cbd22b35112ae04bf85b4dff
# via -r .\requirements.txt
boto3==1.12.21 \
--hash=sha256:50105a25e301e20b361b2b8fafee196a425a4758e51f400a0f381d42e4bd909e \
--hash=sha256:5fe70e656f92e4e649dc4cf05786f57d180e5d491bcb22c80411512ec2b27c15 \
boto3==1.20.44 \
--hash=sha256:26f18ca7411615f33d8d1bf60cc8efe5b331a57b3013d5f8f3587cd5350c27cb \
--hash=sha256:4470f64e4af609ff678055338c96a6f7cbe601d1fb06a4ea7dc8d9223c2e527a \
# via -r .\requirements.txt
botocore==1.15.21 \
--hash=sha256:4aaf6c94bcaace260138d32eae144be1b5d2ddce9ef0f395da32c68e106ff20f \
--hash=sha256:86f7f1c489887f9e3c2ede598e2a30f8bd259c11e8ebe25e897e40231b3f4bc8 \
botocore==1.23.44 \
--hash=sha256:11483a493de4a76ef218d8cd3980c63550d006a0d082c10d53c0954184ca542a \
--hash=sha256:8e5317f84fc1118bff58fa6fa79a9b62083e75a2a9c62feb3ea73694c550b99d \
# via -r .\requirements.txt, boto3, s3transfer
certifi==2019.11.28 \
--hash=sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3 \
@@ -22,6 +22,11 @@ chardet==3.0.4 \
--hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
--hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \
# via -r .\requirements.txt, requests
charset-normalizer==2.0.10 \
--hash=sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd \
--hash=sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455 \
# via
# -r requirements.txt, requests
colorama==0.4.3 \
--hash=sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff \
--hash=sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1 \
@@ -129,30 +134,40 @@ packaging==20.4 \
--hash=sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8 \
--hash=sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181 \
# pytest
pillow==7.0.0 \
--hash=sha256:0a628977ac2e01ca96aaae247ec2bd38e729631ddf2221b4b715446fd45505be \
--hash=sha256:4d9ed9a64095e031435af120d3c910148067087541131e82b3e8db302f4c8946 \
--hash=sha256:54ebae163e8412aff0b9df1e88adab65788f5f5b58e625dc5c7f51eaf14a6837 \
--hash=sha256:5bfef0b1cdde9f33881c913af14e43db69815c7e8df429ceda4c70a5e529210f \
--hash=sha256:5f3546ceb08089cedb9e8ff7e3f6a7042bb5b37c2a95d392fb027c3e53a2da00 \
--hash=sha256:5f7ae9126d16194f114435ebb79cc536b5682002a4fa57fa7bb2cbcde65f2f4d \
--hash=sha256:62a889aeb0a79e50ecf5af272e9e3c164148f4bd9636cc6bcfa182a52c8b0533 \
--hash=sha256:7406f5a9b2fd966e79e6abdaf700585a4522e98d6559ce37fc52e5c955fade0a \
--hash=sha256:8453f914f4e5a3d828281a6628cf517832abfa13ff50679a4848926dac7c0358 \
--hash=sha256:87269cc6ce1e3dee11f23fa515e4249ae678dbbe2704598a51cee76c52e19cda \
--hash=sha256:875358310ed7abd5320f21dd97351d62de4929b0426cdb1eaa904b64ac36b435 \
--hash=sha256:8ac6ce7ff3892e5deaab7abaec763538ffd011f74dc1801d93d3c5fc541feee2 \
--hash=sha256:91b710e3353aea6fc758cdb7136d9bbdcb26b53cefe43e2cba953ac3ee1d3313 \
--hash=sha256:9d2ba4ed13af381233e2d810ff3bab84ef9f18430a9b336ab69eaf3cd24299ff \
--hash=sha256:a62ec5e13e227399be73303ff301f2865bf68657d15ea50b038d25fc41097317 \
--hash=sha256:ab76e5580b0ed647a8d8d2d2daee170e8e9f8aad225ede314f684e297e3643c2 \
--hash=sha256:bf4003aa538af3f4205c5fac56eacaa67a6dd81e454ffd9e9f055fff9f1bc614 \
--hash=sha256:bf598d2e37cf8edb1a2f26ed3fb255191f5232badea4003c16301cb94ac5bdd0 \
--hash=sha256:c18f70dc27cc5d236f10e7834236aff60aadc71346a5bc1f4f83a4b3abee6386 \
--hash=sha256:c5ed816632204a2fc9486d784d8e0d0ae754347aba99c811458d69fcdfd2a2f9 \
--hash=sha256:dc058b7833184970d1248135b8b0ab702e6daa833be14035179f2acb78ff5636 \
--hash=sha256:ff3797f2f16bf9d17d53257612da84dd0758db33935777149b3334c01ff68865 \
# via requirements.txt, imageio
pillow==9.0.0 \
--hash=sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6 \
--hash=sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc \
--hash=sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52 \
--hash=sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4 \
--hash=sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af \
--hash=sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315 \
--hash=sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4 \
--hash=sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281 \
--hash=sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb \
--hash=sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9 \
--hash=sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128 \
--hash=sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105 \
--hash=sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553 \
--hash=sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5 \
--hash=sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d \
--hash=sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6 \
--hash=sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100 \
--hash=sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce \
--hash=sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd \
--hash=sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05 \
--hash=sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f \
--hash=sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f \
--hash=sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7 \
--hash=sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f \
--hash=sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762 \
--hash=sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379 \
--hash=sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee \
--hash=sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925 \
--hash=sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f \
--hash=sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f \
--hash=sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e \
--hash=sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4 \
# via -r requirements.txt
pluggy==0.13.1 \
--hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 \
--hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d \
@@ -191,10 +206,10 @@ psutil==5.8.0 \
--hash=sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3 \
--hash=sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563
# via requirements.txt
py==1.9.0 \
--hash=sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2 \
--hash=sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342 \
# via -r .\requirements.txt, pytest
py==1.11.0 \
--hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 \
--hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 \
# via -r requirements.txt
pyparsing==2.4.7 \
--hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 \
--hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \
@@ -246,13 +261,13 @@ PyYAML==5.4.1 \
--hash=sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df \
--hash=sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc \
# via requirements.txt
requests==2.23.0 \
--hash=sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee \
--hash=sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6 \
# via -r .\requirements.txt
s3transfer==0.3.3 \
--hash=sha256:2482b4259524933a022d59da830f51bd746db62f047d6eb213f2f8855dcb8a13 \
--hash=sha256:921a37e2aefc64145e7b73d50c71bb4f26f46e4c9f414dc648c6245ff92cf7db \
requests==2.27.1 \
--hash=sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61 \
--hash=sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d \
# via -r requirements.txt
s3transfer==0.5.0 \
--hash=sha256:50ed823e1dc5868ad40c8dc92072f757aa0e653a192845c94a3b676f4a62da4c \
--hash=sha256:9c1dc369814391a6bda20ebbf4b70a0f34630592c9aa520856bf384916af2803 \
# boto3
scipy==1.4.1 \
--hash=sha256:00af72998a46c25bdb5824d2b729e7dabec0c765f9deb0b504f928591f5ff9d4 \
@@ -290,10 +305,10 @@ smmap==3.0.5 \
--hash=sha256:7bfcf367828031dc893530a29cb35eb8c8f2d7c8f2d0989354d75d24c8573714 \
--hash=sha256:84c2751ef3072d4f6b2785ec7ee40244c6f45eb934d9e543e2c51f1bd3d54c50 \
# via smmap2
urllib3==1.25.8 \
--hash=sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc \
--hash=sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc \
# via -r .\requirements.txt, botocore, requests
urllib3==1.26.8 \
--hash=sha256:000ca7f471a233c2251c6c7023ee85305721bfdf18621ebff4fd17a8653427ed \
--hash=sha256:0e7c33d9a63e7ddfcb86780aac87befc2fbddf46c58dbb487e0855f7ceec283c \
# via -r requirements.txt
wcwidth==0.2.5 \
--hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \
--hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 \