Add unit tests for the ViewportScreen ndc <-> worldspace utility functions (#1149)
Add ScreenNdcToWorld function to enable round trip testing.
This commit is contained in:
@@ -128,12 +128,12 @@ namespace AzFramework
|
||||
worldPosition, CameraView(cameraState), CameraProjection(cameraState), cameraState.m_viewportSize);
|
||||
}
|
||||
|
||||
AZ::Vector3 ScreenToWorld(
|
||||
const ScreenPoint& screenPosition, const AZ::Matrix4x4& inverseCameraView,
|
||||
const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize)
|
||||
AZ::Vector3 ScreenNDCToWorld(
|
||||
const AZ::Vector2& normalizedScreenPosition, const AZ::Matrix4x4& inverseCameraView,
|
||||
const AZ::Matrix4x4& inverseCameraProjection)
|
||||
{
|
||||
// convert screen space coordinates from <0, 1> to <-1,1> range
|
||||
const auto ndcPosition = NDCFromScreenPoint(screenPosition, viewportSize) * 2.0f - AZ::Vector2::CreateOne();
|
||||
const auto ndcPosition = normalizedScreenPosition * 2.0f - AZ::Vector2::CreateOne();
|
||||
|
||||
// transform ndc space position to clip space
|
||||
const auto clipSpacePosition = inverseCameraProjection * Vector2ToVector4(ndcPosition, -1.0f, 1.0f);
|
||||
@@ -145,6 +145,15 @@ namespace AzFramework
|
||||
return worldPosition;
|
||||
}
|
||||
|
||||
AZ::Vector3 ScreenToWorld(
|
||||
const ScreenPoint& screenPosition, const AZ::Matrix4x4& inverseCameraView,
|
||||
const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize)
|
||||
{
|
||||
const auto normalizedScreenPosition = NDCFromScreenPoint(screenPosition, viewportSize);
|
||||
|
||||
return ScreenNDCToWorld(normalizedScreenPosition, inverseCameraView, inverseCameraProjection);
|
||||
}
|
||||
|
||||
AZ::Vector3 ScreenToWorld(const ScreenPoint& screenPosition, const CameraState& cameraState)
|
||||
{
|
||||
return ScreenToWorld(
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace AzFramework
|
||||
const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection,
|
||||
const AZ::Vector2& viewportSize);
|
||||
|
||||
//! Unprojects a position in screen space to world space.
|
||||
//! Unprojects a position in screen space pixel coordinates to world space.
|
||||
//! Note: The position returned will be on the near clip plane of the camera in world space.
|
||||
AZ::Vector3 ScreenToWorld(const ScreenPoint& screenPosition, const CameraState& cameraState);
|
||||
|
||||
@@ -52,6 +52,12 @@ namespace AzFramework
|
||||
const ScreenPoint& screenPosition, const AZ::Matrix4x4& inverseCameraView,
|
||||
const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize);
|
||||
|
||||
//! Unprojects a position in screen space normalized device coordinates to world space.
|
||||
//! Note: The position returned will be on the near clip plane of the camera in world space.
|
||||
AZ::Vector3 ScreenNDCToWorld(
|
||||
const AZ::Vector2& ndcPosition, const AZ::Matrix4x4& inverseCameraView,
|
||||
const AZ::Matrix4x4& inverseCameraProjection);
|
||||
|
||||
//! Returns the camera projection for the current camera state.
|
||||
AZ::Matrix4x4 CameraProjection(const CameraState& cameraState);
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <AzCore/Math/Matrix3x3.h>
|
||||
#include <AzCore/Math/Matrix4x4.h>
|
||||
#include <AzCore/Math/Transform.h>
|
||||
#include <AzCore/Math/VectorConversions.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Viewport/CameraState.h>
|
||||
#include <AzFramework/Viewport/ViewportScreen.h>
|
||||
@@ -23,6 +24,15 @@
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
// transform a point from normalized device coordinates to world space, and then from world space back to normalized device coordinates
|
||||
AZ::Vector2 ScreenNDCToWorldToScreenNDC(
|
||||
const AZ::Vector2& ndcPoint, const AzFramework::CameraState& cameraState)
|
||||
{
|
||||
const auto worldResult = AzFramework::ScreenNDCToWorld(ndcPoint, InverseCameraView(cameraState), InverseCameraProjection(cameraState));
|
||||
const auto ndcResult = AzFramework::WorldToScreenNDC(worldResult, CameraView(cameraState), CameraProjection(cameraState));
|
||||
return AZ::Vector3ToVector2(ndcResult);
|
||||
}
|
||||
|
||||
// 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, const AzFramework::CameraState& cameraState)
|
||||
@@ -30,7 +40,8 @@ namespace UnitTest
|
||||
const auto worldResult = AzFramework::ScreenToWorld(screenPoint, cameraState);
|
||||
return AzFramework::WorldToScreen(worldResult, cameraState);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ScreenPoint tests
|
||||
TEST(ViewportScreen, WorldToScreenAndScreenToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin)
|
||||
{
|
||||
using AzFramework::ScreenPoint;
|
||||
@@ -38,8 +49,6 @@ namespace UnitTest
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraPosition = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
|
||||
// note: nearClip is 0.1 - the world space value returned will be aligned to the near clip
|
||||
// plane of the camera so use that to confirm the mapping to/from is correct
|
||||
const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions);
|
||||
{
|
||||
const auto expectedScreenPoint = ScreenPoint{600, 450};
|
||||
@@ -81,6 +90,8 @@ namespace UnitTest
|
||||
EXPECT_EQ(resultScreenPoint, expectedScreenPoint);
|
||||
}
|
||||
|
||||
// note: nearClip is 0.1 - the world space value returned will be aligned to the near clip
|
||||
// plane of the camera so use that to confirm the mapping to/from is correct
|
||||
TEST(ViewportScreen, ScreenToWorldReturnsPositionOnNearClipPlaneInWorldSpace)
|
||||
{
|
||||
using AzFramework::ScreenPoint;
|
||||
@@ -94,7 +105,75 @@ namespace UnitTest
|
||||
const auto worldResult = AzFramework::ScreenToWorld(ScreenPoint{400, 300}, cameraState);
|
||||
EXPECT_THAT(worldResult, IsClose(AZ::Vector3(10.1f, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// NDC tests
|
||||
TEST(ViewportScreen, WorldToScreenNDCAndScreenNDCToWorldReturnsTheSameValueIdentityCameraOffsetFromOrigin)
|
||||
{
|
||||
using NdcPoint = AZ::Vector2;
|
||||
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraPosition = AZ::Vector3::CreateAxisY(-10.0f);
|
||||
|
||||
const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions);
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{0.75f, 0.75f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{0.5f, 0.5f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{0.0f, 0.0f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
{
|
||||
const auto expectedNdcPoint = NdcPoint{1.0f, 1.0f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ViewportScreen, WorldToScreenNDCAndScreenNDCToWorldReturnsTheSameValueOrientatedCamera)
|
||||
{
|
||||
using NdcPoint = AZ::Vector2;
|
||||
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraTransform =
|
||||
AZ::Transform::CreateRotationX(AZ::DegToRad(45.0f)) * AZ::Transform::CreateRotationZ(AZ::DegToRad(90.0f));
|
||||
|
||||
const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions);
|
||||
|
||||
const auto expectedNdcPoint = NdcPoint{0.25f, 0.5f};
|
||||
const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState);
|
||||
EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint));
|
||||
}
|
||||
|
||||
// note: nearClip is 0.1 - the world space value returned will be aligned to the near clip
|
||||
// plane of the camera so use that to confirm the mapping to/from is correct
|
||||
TEST(ViewportScreen, ScreenNDCToWorldReturnsPositionOnNearClipPlaneInWorldSpace)
|
||||
{
|
||||
using NdcPoint = AZ::Vector2;
|
||||
|
||||
const auto screenDimensions = AZ::Vector2(800.0f, 600.0f);
|
||||
const auto cameraTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f)) *
|
||||
AZ::Transform::CreateRotationZ(AZ::DegToRad(-90.0f));
|
||||
|
||||
const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions);
|
||||
|
||||
const auto worldResult = AzFramework::ScreenNDCToWorld(NdcPoint{0.5f, 0.5f}, InverseCameraView(cameraState), InverseCameraProjection(cameraState));
|
||||
EXPECT_THAT(worldResult, IsClose(AZ::Vector3(10.1f, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ScreenVector tests
|
||||
TEST(ViewportScreen, SubstractingScreenPointGivesScreenVector)
|
||||
{
|
||||
using AzFramework::ScreenPoint;
|
||||
@@ -220,6 +299,8 @@ namespace UnitTest
|
||||
EXPECT_NEAR(AzFramework::ScreenVectorLength(ScreenVector(12, 15)), 19.20937f, 0.001f);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Other tests
|
||||
TEST(ViewportScreen, CanGetCameraTransformFromCameraViewAndBack)
|
||||
{
|
||||
const auto screenDimensions = AZ::Vector2(1024.0f, 768.0f);
|
||||
|
||||
Reference in New Issue
Block a user