Fixes for viewport selection issues (#5494)

* improvements to editor selection in the viewport

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* fix issue with being able to select icons that are not showing for entities inside entity containers

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* update comment after review feedback

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* updates to viewport picking code to simplify the api

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* add test to replicate near clip intersection issue

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* small tidy-up changes

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* updates to how we perform world to screen and screen to world calculations, added test coverage and some tidy-up

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* add some more tests for ViewportInteractionImpl

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* minor tweaks before PR feedback

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* fix typo in fix

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* fix for manipulator test framework tests

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* updates to RPI::View and RenderPipeline after review feedback from VickyAtAZ

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* add constexpr to ScreenPoint, ScreenVector and ScreenSize initializing constructors

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* add PrintTo functions for Screen* types

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* downgrade error to warning temporarily

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* check incoming view is null

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>

* remove pragma optimize off

Signed-off-by: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com>
This commit is contained in:
Tom Hulton-Harrop
2021-11-17 17:12:21 +00:00
committed by GitHub
parent ef16207ac5
commit e34ea3bcaa
45 changed files with 680 additions and 309 deletions
@@ -132,17 +132,22 @@ namespace AZ
//! Returns the masked occlusion culling interface
MaskedOcclusionCulling* GetMaskedOcclusionCulling();
//! This is called by RenderPipeline when this view is added to the pipeline.
void OnAddToRenderPipeline();
private:
View() = delete;
View(const AZ::Name& name, UsageFlags usage);
//! Sorts the finalized draw lists in this view
void SortFinalizedDrawLists();
//! Sorts a drawList using the sort function from a pass with the corresponding drawListTag
void SortDrawList(RHI::DrawList& drawList, RHI::DrawListTag tag);
//! Attempt to create a shader resource group.
void TryCreateShaderResourceGroup();
AZ::Name m_name;
UsageFlags m_usageFlags;
@@ -178,6 +178,10 @@ namespace AZ
pipelineViews.m_views.resize(1);
}
ViewPtr previousView = pipelineViews.m_views[0];
if (view)
{
view->OnAddToRenderPipeline();
}
pipelineViews.m_views[0] = view;
if (previousView)
@@ -238,6 +242,7 @@ namespace AZ
pipelineViews.m_type = PipelineViewType::Transient;
}
view->SetPassesByDrawList(&pipelineViews.m_passesByDrawList);
view->OnAddToRenderPipeline();
pipelineViews.m_views.push_back(view);
}
}
+56 -26
View File
@@ -47,18 +47,14 @@ namespace AZ
{
AZ_Assert(!name.IsEmpty(), "invalid name");
// Set default matrixes.
// Set default matrices
SetWorldToViewMatrix(AZ::Matrix4x4::CreateIdentity());
AZ::Matrix4x4 viewToClipMatrix;
AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::Constants::HalfPi, 1, 0.1f, 1000.f, true);
SetViewToClipMatrix(viewToClipMatrix);
Data::Asset<ShaderAsset> viewSrgShaderAsset = RPISystemInterface::Get()->GetCommonShaderAssetForSrgs();
TryCreateShaderResourceGroup();
if (viewSrgShaderAsset.IsReady())
{
m_shaderResourceGroup = ShaderResourceGroup::Create(viewSrgShaderAsset, RPISystemInterface::Get()->GetViewSrgLayout()->GetName());
}
#if AZ_TRAIT_MASKED_OCCLUSION_CULLING_SUPPORTED
m_maskedOcclusionCulling = MaskedOcclusionCulling::Create();
m_maskedOcclusionCulling->SetResolution(MaskedSoftwareOcclusionCullingWidth, MaskedSoftwareOcclusionCullingHeight);
@@ -369,16 +365,19 @@ namespace AZ
{
if (m_clipSpaceOffset.IsZero())
{
Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix;
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix);
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix);
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull());
if (m_shaderResourceGroup)
{
Matrix4x4 worldToClipPrevMatrix = m_viewToClipPrevMatrix * m_worldToViewPrevMatrix;
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, worldToClipPrevMatrix);
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, m_worldToClipMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, m_viewToClipMatrix);
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, m_clipToWorldMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, m_viewToClipMatrix.GetInverseFull());
}
}
else
{
// Offset the current and previous frame clip matricies
// Offset the current and previous frame clip matrices
Matrix4x4 offsetViewToClipMatrix = m_viewToClipMatrix;
offsetViewToClipMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX());
offsetViewToClipMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY());
@@ -387,27 +386,33 @@ namespace AZ
offsetViewToClipPrevMatrix.SetElement(0, 2, m_clipSpaceOffset.GetX());
offsetViewToClipPrevMatrix.SetElement(1, 2, m_clipSpaceOffset.GetY());
// Build other matricies dependent on the view to clip matricies
// Build other matrices dependent on the view to clip matrices
Matrix4x4 offsetWorldToClipMatrix = offsetViewToClipMatrix * m_worldToViewMatrix;
Matrix4x4 offsetWorldToClipPrevMatrix = offsetViewToClipPrevMatrix * m_worldToViewPrevMatrix;
Matrix4x4 offsetClipToViewMatrix = offsetViewToClipMatrix.GetInverseFull();
Matrix4x4 offsetClipToWorldMatrix = m_viewToWorldMatrix * offsetClipToViewMatrix;
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix);
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix);
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull());
if (m_shaderResourceGroup)
{
m_shaderResourceGroup->SetConstant(m_worldToClipPrevMatrixConstantIndex, offsetWorldToClipPrevMatrix);
m_shaderResourceGroup->SetConstant(m_viewProjectionMatrixConstantIndex, offsetWorldToClipMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixConstantIndex, offsetViewToClipMatrix);
m_shaderResourceGroup->SetConstant(m_clipToWorldMatrixConstantIndex, offsetClipToWorldMatrix);
m_shaderResourceGroup->SetConstant(m_projectionMatrixInverseConstantIndex, offsetViewToClipMatrix.GetInverseFull());
}
}
m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position);
m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix);
m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull());
m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ);
m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants);
if (m_shaderResourceGroup)
{
m_shaderResourceGroup->SetConstant(m_worldPositionConstantIndex, m_position);
m_shaderResourceGroup->SetConstant(m_viewMatrixConstantIndex, m_worldToViewMatrix);
m_shaderResourceGroup->SetConstant(m_viewMatrixInverseConstantIndex, m_worldToViewMatrix.GetInverseFull());
m_shaderResourceGroup->SetConstant(m_zConstantsConstantIndex, m_nearZ_farZ_farZTimesNearZ_farZMinusNearZ);
m_shaderResourceGroup->SetConstant(m_unprojectionConstantsIndex, m_unprojectionConstants);
m_shaderResourceGroup->Compile();
m_shaderResourceGroup->Compile();
}
m_viewToClipPrevMatrix = m_viewToClipMatrix;
m_worldToViewPrevMatrix = m_worldToViewMatrix;
@@ -426,5 +431,30 @@ namespace AZ
{
return m_maskedOcclusionCulling;
}
void View::TryCreateShaderResourceGroup()
{
if (!m_shaderResourceGroup)
{
if (auto rpiSystemInterface = RPISystemInterface::Get())
{
if (Data::Asset<ShaderAsset> viewSrgShaderAsset = rpiSystemInterface->GetCommonShaderAssetForSrgs();
viewSrgShaderAsset.IsReady())
{
m_shaderResourceGroup =
ShaderResourceGroup::Create(viewSrgShaderAsset, rpiSystemInterface->GetViewSrgLayout()->GetName());
}
}
}
}
void View::OnAddToRenderPipeline()
{
TryCreateShaderResourceGroup();
if (!m_shaderResourceGroup)
{
AZ_Warning("RPI::View", false, "Shader Resource Group failed to initialize");
}
}
} // namespace RPI
} // namespace AZ
@@ -80,6 +80,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
PRIVATE
AZ::AzTest
AZ::AzTestShared
AZ::AzFrameworkTestShared
Gem::AtomToolsFramework.Static
Gem::Atom_Utils.TestUtils.Static
)
@@ -21,6 +21,7 @@
#include <AzFramework/Windowing/WindowBus.h>
#include <AzCore/Component/TickBus.h>
#include <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
#include <AtomToolsFramework/Viewport/ViewportInteractionImpl.h>
namespace AtomToolsFramework
{
@@ -30,8 +31,8 @@ namespace AtomToolsFramework
//! @see AZ::RPI::ViewportContext for Atom's API for setting up
class RenderViewportWidget
: public QWidget
, public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler
, public AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler
, public AzToolsFramework::ViewportInteraction::ViewportInteractionRequests
, public AzFramework::WindowRequestBus::Handler
, protected AzFramework::InputChannelEventListener
, protected AZ::TickBus::Handler
@@ -90,11 +91,11 @@ namespace AtomToolsFramework
//! Input processing is enabled by default.
void SetInputProcessingEnabled(bool enabled);
// AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler overrides ...
// ViewportInteractionRequests overrides ...
AzFramework::CameraState GetCameraState() override;
AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override;
AZStd::optional<AZ::Vector3> ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) override;
AZStd::optional<AzToolsFramework::ViewportInteraction::ProjectedViewportRay> ViewportScreenToWorldRay(
AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) override;
AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportScreenToWorldRay(
const AzFramework::ScreenPoint& screenPosition) override;
float DeviceScalingFactor() override;
@@ -149,5 +150,7 @@ namespace AtomToolsFramework
AZ::ScriptTimePoint m_time;
// Maps our internal Qt events into AzFramework InputChannels for our ViewportControllerList.
AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr;
// Implementation of ViewportInteractionRequests (handles viewport picking operations).
AZStd::unique_ptr<ViewportInteractionImpl> m_viewportInteractionImpl;
};
} //namespace AtomToolsFramework
@@ -0,0 +1,42 @@
/*
* 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 <Atom/RPI.Public/Base.h>
#include <Atom/RPI.Public/View.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AtomToolsFramework
{
//! A concrete implementation of the ViewportInteractionRequestBus.
//! Primarily concerned with picking (screen to world and world to screen transformations).
class ViewportInteractionImpl : public AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler
{
public:
explicit ViewportInteractionImpl(AZ::RPI::ViewPtr viewPtr);
void Connect(AzFramework::ViewportId viewportId);
void Disconnect();
// ViewportInteractionRequestBus overrides ...
AzFramework::CameraState GetCameraState() override;
AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) override;
AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) override;
AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportScreenToWorldRay(
const AzFramework::ScreenPoint& screenPosition) override;
float DeviceScalingFactor() override;
AZStd::function<AzFramework::ScreenSize()> m_screenSizeFn; //! Callback to determine the screen size.
AZStd::function<float()> m_deviceScalingFactorFn; //! Callback to determine the device scaling factor.
private:
AZ::RPI::ViewPtr m_viewPtr;
};
} // namespace AtomToolsFramework
@@ -75,7 +75,11 @@ namespace AtomToolsFramework
m_defaultCamera = AZ::RPI::View::CreateView(cameraName, AZ::RPI::View::UsageFlags::UsageCamera);
AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get()->PushView(m_viewportContext->GetName(), m_defaultCamera);
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(GetId());
m_viewportInteractionImpl = AZStd::make_unique<ViewportInteractionImpl>(m_defaultCamera);
m_viewportInteractionImpl->m_deviceScalingFactorFn = [this] { return aznumeric_cast<float>(devicePixelRatioF()); };
m_viewportInteractionImpl->m_screenSizeFn = [this] { return AzFramework::ScreenSize(width(), height()); };
m_viewportInteractionImpl->Connect(id);
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler::BusConnect(GetId());
AzFramework::InputChannelEventListener::Connect();
AZ::TickBus::Handler::BusConnect();
@@ -107,7 +111,7 @@ namespace AtomToolsFramework
AZ::TickBus::Handler::BusDisconnect();
AzFramework::InputChannelEventListener::Disconnect();
AzToolsFramework::ViewportInteraction::ViewportMouseCursorRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusDisconnect();
m_viewportInteractionImpl->Disconnect();
}
void RenderViewportWidget::LockRenderTargetSize(uint32_t width, uint32_t height)
@@ -278,77 +282,23 @@ namespace AtomToolsFramework
AzFramework::CameraState RenderViewportWidget::GetCameraState()
{
AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView();
if (currentView == nullptr)
{
return {};
}
// Build camera state from Atom camera transforms
AzFramework::CameraState cameraState = AzFramework::CreateCameraFromWorldFromViewMatrix(
currentView->GetViewToWorldMatrix(),
AZ::Vector2{aznumeric_cast<float>(width()), aznumeric_cast<float>(height())}
);
AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, currentView->GetViewToClipMatrix());
// Convert from Z-up
AZStd::swap(cameraState.m_forward, cameraState.m_up);
cameraState.m_forward = -cameraState.m_forward;
return cameraState;
return m_viewportInteractionImpl->GetCameraState();
}
AzFramework::ScreenPoint RenderViewportWidget::ViewportWorldToScreen(const AZ::Vector3& worldPosition)
{
if (AZ::RPI::ViewPtr currentView = m_viewportContext->GetDefaultView();
currentView == nullptr)
{
return AzFramework::ScreenPoint(0, 0);
}
return AzFramework::WorldToScreen(worldPosition, GetCameraState());
return m_viewportInteractionImpl->ViewportWorldToScreen(worldPosition);
}
AZStd::optional<AZ::Vector3> RenderViewportWidget::ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth)
AZ::Vector3 RenderViewportWidget::ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition)
{
const auto& cameraProjection = m_viewportContext->GetCameraProjectionMatrix();
const auto& cameraView = m_viewportContext->GetCameraViewMatrix();
const AZ::Vector4 normalizedScreenPosition {
screenPosition.m_x * 2.f / width() - 1.0f,
(height() - screenPosition.m_y) * 2.f / height() - 1.0f,
1.f - depth, // [GFX TODO] [ATOM-1501] Currently we always assume reverse depth
1.f
};
AZ::Matrix4x4 worldFromScreen = cameraProjection * cameraView;
worldFromScreen.InvertFull();
const AZ::Vector4 projectedPosition = worldFromScreen * normalizedScreenPosition;
if (projectedPosition.GetW() == 0.0f)
{
return {};
}
return projectedPosition.GetAsVector3() / projectedPosition.GetW();
return m_viewportInteractionImpl->ViewportScreenToWorld(screenPosition);
}
AZStd::optional<AzToolsFramework::ViewportInteraction::ProjectedViewportRay> RenderViewportWidget::ViewportScreenToWorldRay(
AzToolsFramework::ViewportInteraction::ProjectedViewportRay RenderViewportWidget::ViewportScreenToWorldRay(
const AzFramework::ScreenPoint& screenPosition)
{
auto pos0 = ViewportScreenToWorld(screenPosition, 0.f);
auto pos1 = ViewportScreenToWorld(screenPosition, 1.f);
if (!pos0.has_value() || !pos1.has_value())
{
return {};
}
pos0 = m_viewportContext->GetDefaultView()->GetViewToWorldMatrix().GetTranslation();
AZ::Vector3 rayOrigin = pos0.value();
AZ::Vector3 rayDirection = pos1.value() - pos0.value();
rayDirection.Normalize();
return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{rayOrigin, rayDirection};
return m_viewportInteractionImpl->ViewportScreenToWorldRay(screenPosition);
}
float RenderViewportWidget::DeviceScalingFactor()
@@ -0,0 +1,61 @@
/*
* 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 <AtomToolsFramework/Viewport/ViewportInteractionImpl.h>
#include <AzFramework/Viewport/ViewportScreen.h>
namespace AtomToolsFramework
{
ViewportInteractionImpl::ViewportInteractionImpl(AZ::RPI::ViewPtr viewPtr)
: m_viewPtr(AZStd::move(viewPtr))
{
}
void ViewportInteractionImpl::Connect(const AzFramework::ViewportId viewportId)
{
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(viewportId);
}
void ViewportInteractionImpl::Disconnect()
{
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusDisconnect();
}
AzFramework::CameraState ViewportInteractionImpl::GetCameraState()
{
// build camera state from atom camera transforms
AzFramework::CameraState cameraState =
AzFramework::CreateDefaultCamera(m_viewPtr->GetCameraTransform(), AzFramework::Vector2FromScreenSize(m_screenSizeFn()));
AzFramework::SetCameraClippingVolumeFromPerspectiveFovMatrixRH(cameraState, m_viewPtr->GetViewToClipMatrix());
return cameraState;
}
AzFramework::ScreenPoint ViewportInteractionImpl::ViewportWorldToScreen(const AZ::Vector3& worldPosition)
{
return AzFramework::WorldToScreen(worldPosition, GetCameraState());
}
AZ::Vector3 ViewportInteractionImpl::ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition)
{
return AzFramework::ScreenToWorld(screenPosition, GetCameraState());
}
AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteractionImpl::ViewportScreenToWorldRay(
const AzFramework::ScreenPoint& screenPosition)
{
const AzFramework::CameraState cameraState = GetCameraState();
const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPosition, cameraState);
const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized();
return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection };
}
float ViewportInteractionImpl::DeviceScalingFactor()
{
return m_deviceScalingFactorFn();
}
} // namespace AtomToolsFramework
@@ -12,16 +12,25 @@
namespace UnitTest
{
class AtomToolsFrameworkTestEnvironment : public AZ::Test::ITestEnvironment
{
protected:
void SetupEnvironment() override
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
void TeardownEnvironment() override
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
};
class AtomToolsFrameworkTest : public ::testing::Test
{
protected:
void SetUp() override
{
if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Create(AZ::SystemAllocator::Descriptor());
}
m_assetSystemStub.Activate();
RegisterSourceAsset("objects/upgrades/materials/supercondor.material");
@@ -38,11 +47,6 @@ namespace UnitTest
void TearDown() override
{
m_assetSystemStub.Deactivate();
if (AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
}
}
void RegisterSourceAsset(const AZStd::string& path)
@@ -73,5 +77,5 @@ namespace UnitTest
ASSERT_EQ(AtomToolsFramework::GetExteralReferencePath("d:/project/assets/objects/upgrades/materials/supercondor.material", "d:/project/assets/materials/condor.material", 0), "materials/condor.material");
}
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
AZ_UNIT_TEST_HOOK(new AtomToolsFrameworkTestEnvironment);
} // namespace UnitTest
@@ -0,0 +1,172 @@
/*
* 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 <Atom/RPI.Public/View.h>
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzCore/Math/MatrixUtils.h>
#include <AzCore/Name/NameDictionary.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <Tests/Utils/Printers.h>
namespace UnitTest
{
class ViewportInteractionImplFixture : public ::testing::Test
{
public:
static inline constexpr AzFramework::ViewportId TestViewportId = 1234;
static inline constexpr AzFramework::ScreenSize ScreenDimensions = AzFramework::ScreenSize(1280, 720);
static AzFramework::ScreenPoint ScreenCenter()
{
const auto halfScreenDimensions = ScreenDimensions * 0.5f;
return AzFramework::ScreenPoint(halfScreenDimensions.m_width, halfScreenDimensions.m_height);
}
void SetUp() override
{
AZ::NameDictionary::Create();
m_view = AZ::RPI::View::CreateView(AZ::Name("TestView"), AZ::RPI::View::UsageCamera);
const auto aspectRatio = aznumeric_cast<float>(ScreenDimensions.m_width) / aznumeric_cast<float>(ScreenDimensions.m_height);
AZ::Matrix4x4 viewToClipMatrix;
AZ::MakePerspectiveFovMatrixRH(viewToClipMatrix, AZ::DegToRad(60.0f), aspectRatio, 0.1f, 1000.f, true);
m_view->SetViewToClipMatrix(viewToClipMatrix);
m_viewportInteractionImpl = AZStd::make_unique<AtomToolsFramework::ViewportInteractionImpl>(m_view);
m_viewportInteractionImpl->m_deviceScalingFactorFn = []
{
return 1.0f;
};
m_viewportInteractionImpl->m_screenSizeFn = []
{
return ScreenDimensions;
};
m_viewportInteractionImpl->Connect(TestViewportId);
}
void TearDown() override
{
m_viewportInteractionImpl->Disconnect();
m_viewportInteractionImpl.reset();
m_view.reset();
AZ::NameDictionary::Destroy();
}
AZ::RPI::ViewPtr m_view;
AZStd::unique_ptr<AtomToolsFramework::ViewportInteractionImpl> m_viewportInteractionImpl;
};
// transform a point from screen space to world space, and then from world space back to screen space
AzFramework::ScreenPoint ScreenToWorldToScreen(
const AzFramework::ScreenPoint& screenPoint,
AzToolsFramework::ViewportInteraction::ViewportInteractionRequests& viewportInteractionRequests)
{
const auto worldResult = viewportInteractionRequests.ViewportScreenToWorld(screenPoint);
return viewportInteractionRequests.ViewportWorldToScreen(worldResult);
}
TEST_F(ViewportInteractionImplFixture, ViewportInteractionRequestsMapsFromScreenToWorldAndBack)
{
using AzFramework::ScreenPoint;
m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(10.0f, 0.0f, 5.0f)));
{
const auto expectedScreenPoint = ScreenPoint{ 600, 450 };
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
{
auto expectedScreenPoint = ScreenCenter();
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
{
const auto expectedScreenPoint = ScreenPoint{ 0, 0 };
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
{
const auto expectedScreenPoint = ScreenPoint{ ScreenDimensions.m_width, ScreenDimensions.m_height };
const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, *m_viewportInteractionImpl);
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
}
}
TEST_F(ViewportInteractionImplFixture, ScreenToWorldReturnsPositionOnNearClipPlaneInWorldSpace)
{
m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-90.0f)), AZ::Vector3(20.0f, 0.0f, 0.0f)));
const auto worldResult = m_viewportInteractionImpl->ViewportScreenToWorld(ScreenCenter());
EXPECT_THAT(worldResult, IsClose(AZ::Vector3(20.1f, 0.0f, 0.0f)));
}
// note: values produced by reproducing in the editor viewport
TEST_F(ViewportInteractionImplFixture, WorldToScreenGivesExpectedScreenCoordinates)
{
using AzFramework::ScreenPoint;
{
m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(160.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-18.0f)),
AZ::Vector3(-21.0f, 2.5f, 6.0f)));
const auto screenResult = m_viewportInteractionImpl->ViewportWorldToScreen(AZ::Vector3(-21.0f, -1.5f, 5.0f));
EXPECT_EQ(screenResult, ScreenPoint(420, 326));
}
{
m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(175.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-90.0f)),
AZ::Vector3(-10.0f, -11.0f, 2.5f)));
const auto screenResult = m_viewportInteractionImpl->ViewportWorldToScreen(AZ::Vector3(-10.0f, -10.5f, 0.5f));
EXPECT_EQ(screenResult, ScreenPoint(654, 515));
}
{
m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(70.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(65.0f)),
AZ::Vector3(-22.5f, -10.0f, 1.5f)));
const auto screenResult = m_viewportInteractionImpl->ViewportWorldToScreen(AZ::Vector3(-23.0f, -9.5f, 3.0f));
EXPECT_EQ(screenResult, ScreenPoint(754, 340));
}
}
TEST_F(ViewportInteractionImplFixture, ScreenToWorldRayGivesGivesExpectedOriginAndDirection)
{
using AzFramework::ScreenPoint;
m_view->SetCameraTransform(AZ::Matrix3x4::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(34.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-24.0f)),
AZ::Vector3(-9.3f, -9.8f, 4.0f)));
const auto ray = m_viewportInteractionImpl->ViewportScreenToWorldRay(ScreenPoint(832, 226));
float unused;
auto intersection = AZ::Intersect::IntersectRaySphere(ray.origin, ray.direction, AZ::Vector3(-14.0f, 5.7f, 0.75f), 0.5f, unused);
EXPECT_EQ(intersection, AZ::Intersect::SphereIsectTypes::ISECT_RAY_SPHERE_ISECT);
}
} // namespace UnitTest
@@ -28,6 +28,7 @@ set(FILES
Include/AtomToolsFramework/Util/MaterialPropertyUtil.h
Include/AtomToolsFramework/Util/Util.h
Include/AtomToolsFramework/Viewport/RenderViewportWidget.h
Include/AtomToolsFramework/Viewport/ViewportInteractionImpl.h
Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h
Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h
Include/AtomToolsFramework/Window/AtomToolsMainWindow.h
@@ -55,6 +56,7 @@ set(FILES
Source/Util/Util.cpp
Source/Viewport/RenderViewportWidget.cpp
Source/Viewport/ModularViewportCameraController.cpp
Source/Viewport/ViewportInteractionImpl.cpp
Source/Window/AtomToolsMainWindow.cpp
Source/Window/AtomToolsMainWindowSystemComponent.cpp
Source/Window/AtomToolsMainWindowSystemComponent.h
@@ -8,4 +8,5 @@
set(FILES
Tests/AtomToolsFrameworkTest.cpp
Tests/ViewportInteractionImplTests.cpp
)