diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h index c0fa1ad631..1529d760e5 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ScreenGeometry.h @@ -8,11 +8,10 @@ #pragma once -#include -#include -#include -#include #include +#include +#include +#include namespace AZ { @@ -27,7 +26,11 @@ namespace AzFramework AZ_TYPE_INFO(ScreenPoint, "{8472B6C2-527F-44FC-87F8-C226B1A57A97}"); ScreenPoint() = default; - ScreenPoint(int x, int y) : m_x(x), m_y(y) {} + ScreenPoint(int x, int y) + : m_x(x) + , m_y(y) + { + } int m_x; //!< X screen position. int m_y; //!< Y screen position. @@ -42,7 +45,11 @@ namespace AzFramework AZ_TYPE_INFO(ScreenVector, "{1EAA2C62-8FDB-4A28-9FE3-1FA4F1418894}"); ScreenVector() = default; - ScreenVector(int x, int y) : m_x(x), m_y(y) {} + ScreenVector(int x, int y) + : m_x(x) + , m_y(y) + { + } int m_x; //!< X screen delta. int m_y; //!< Y screen delta. @@ -71,14 +78,14 @@ namespace AzFramework inline const ScreenPoint operator+(const ScreenPoint& lhs, const ScreenVector& rhs) { - ScreenPoint result{lhs}; + ScreenPoint result{ lhs }; result += rhs; return result; } inline const ScreenPoint operator-(const ScreenPoint& lhs, const ScreenVector& rhs) { - ScreenPoint result{lhs}; + ScreenPoint result{ lhs }; result -= rhs; return result; } @@ -99,14 +106,14 @@ namespace AzFramework inline const ScreenVector operator+(const ScreenVector& lhs, const ScreenVector& rhs) { - ScreenVector result{lhs}; + ScreenVector result{ lhs }; result += rhs; return result; } inline const ScreenVector operator-(const ScreenVector& lhs, const ScreenVector& rhs) { - ScreenVector result{lhs}; + ScreenVector result{ lhs }; result -= rhs; return result; } @@ -131,23 +138,25 @@ namespace AzFramework return !operator==(lhs, rhs); } + inline ScreenVector& operator*=(ScreenVector& lhs, const float rhs) + { + lhs.m_x = aznumeric_cast(AZStd::lround(aznumeric_cast(lhs.m_x) * rhs)); + lhs.m_y = aznumeric_cast(AZStd::lround(aznumeric_cast(lhs.m_y) * rhs)); + return lhs; + } + + inline const ScreenVector operator*(const ScreenVector& lhs, const float rhs) + { + ScreenVector result{ lhs }; + result *= rhs; + return result; + } + inline float ScreenVectorLength(const ScreenVector& screenVector) { return aznumeric_cast(AZStd::sqrt(screenVector.m_x * screenVector.m_x + screenVector.m_y * screenVector.m_y)); } - inline ScreenPoint ScreenPointFromNDC(const AZ::Vector3& screenNDC, const AZ::Vector2& viewportSize) - { - return ScreenPoint( - aznumeric_caster(AZStd::round(screenNDC.GetX() * viewportSize.GetX())), - aznumeric_caster(AZStd::round((1.0f - screenNDC.GetY()) * viewportSize.GetY()))); - } - - inline AZ::Vector2 NDCFromScreenPoint(const ScreenPoint& screenPoint, const AZ::Vector2& viewportSize) - { - return AZ::Vector2(aznumeric_cast(screenPoint.m_x), viewportSize.GetY() - aznumeric_cast(screenPoint.m_y)) / viewportSize; - } - //! Return an AZ::Vector2 from a ScreenPoint. inline AZ::Vector2 Vector2FromScreenPoint(const ScreenPoint& screenPoint) { diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp index a94b806ad3..afd16a6c23 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.cpp @@ -23,7 +23,7 @@ namespace AzFramework // y-up, z into the screen, x-left // // x -> -x - // y -> z + // y -> z // z -> y // // the same transform can be used to go to/from z-up - the only difference is the order of @@ -37,15 +37,15 @@ namespace AzFramework // yaw = AZ::Matrix4x4::CreateRotationZ(AZ::DegToRad(180.0f)); // conversion = pitch * yaw return AZ::Matrix4x4::CreateFromColumns( - AZ::Vector4(-1.0f, 0.0f, 0.0f, 0.0f), AZ::Vector4(0.0f, 0.0f, 1.0f, .0f), - AZ::Vector4(0.0f, 1.0f, 0.0f, 0.0f), AZ::Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + AZ::Vector4(-1.0f, 0.0f, 0.0f, 0.0f), AZ::Vector4(0.0f, 0.0f, 1.0f, 0.0f), AZ::Vector4(0.0f, 1.0f, 0.0f, 0.0f), + AZ::Vector4(0.0f, 0.0f, 0.0f, 1.0f)); } AZ::Matrix4x4 CameraTransform(const CameraState& cameraState) { return AZ::Matrix4x4::CreateFromColumns( - AZ::Vector3ToVector4(cameraState.m_side), AZ::Vector3ToVector4(cameraState.m_forward), - AZ::Vector3ToVector4(cameraState.m_up), AZ::Vector3ToVector4(cameraState.m_position, 1.0f)); + AZ::Vector3ToVector4(cameraState.m_side), AZ::Vector3ToVector4(cameraState.m_forward), AZ::Vector3ToVector4(cameraState.m_up), + AZ::Vector3ToVector4(cameraState.m_position, 1.0f)); } AZ::Matrix4x4 CameraView(const CameraState& cameraState) @@ -63,8 +63,7 @@ namespace AzFramework AZ::Matrix4x4 CameraProjection(const CameraState& cameraState) { return AZ::Matrix4x4::CreateProjection( - cameraState.VerticalFovRadian(), AspectRatio(cameraState.m_viewportSize), cameraState.m_nearClip, - cameraState.m_farClip); + cameraState.VerticalFovRadian(), AspectRatio(cameraState.m_viewportSize), cameraState.m_nearClip, cameraState.m_farClip); } AZ::Matrix4x4 InverseCameraProjection(const CameraState& cameraState) @@ -93,12 +92,11 @@ namespace AzFramework const auto cameraWorldTransform = AZ::Transform::CreateFromMatrix3x3AndTranslation( AZ::Matrix3x3::CreateFromMatrix4x4(worldFromView), worldFromView.GetTranslation()); return AZ::ViewFrustumAttributes( - cameraWorldTransform, AspectRatio(cameraState.m_viewportSize), cameraState.m_fovOrZoom, - cameraState.m_nearClip, cameraState.m_farClip); + cameraWorldTransform, AspectRatio(cameraState.m_viewportSize), cameraState.m_fovOrZoom, cameraState.m_nearClip, + cameraState.m_farClip); } - AZ::Vector3 WorldToScreenNDC( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection) + AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection) { // transform the world space position to clip space const auto clipSpacePosition = cameraProjection * cameraView * AZ::Vector3ToVector4(worldPosition, 1.0f); @@ -108,25 +106,24 @@ namespace AzFramework return (AZ::Vector4ToVector3(ndcPosition) + AZ::Vector3::CreateOne()) * 0.5f; } - ScreenPoint WorldToScreen( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection, + const AZ::Vector3& worldPosition, + const AZ::Matrix4x4& cameraView, + const AZ::Matrix4x4& cameraProjection, const AZ::Vector2& viewportSize) { - const auto ndcNormalizedPosition = WorldToScreenNDC(worldPosition, cameraView, cameraProjection); + const auto ndcNormalizedPosition = WorldToScreenNdc(worldPosition, cameraView, cameraProjection); // scale ndc position by screen dimensions to return screen position - return ScreenPointFromNDC(ndcNormalizedPosition, viewportSize); + return ScreenPointFromNdc(AZ::Vector3ToVector2(ndcNormalizedPosition), viewportSize); } ScreenPoint WorldToScreen(const AZ::Vector3& worldPosition, const CameraState& cameraState) { - return WorldToScreen( - worldPosition, CameraView(cameraState), CameraProjection(cameraState), cameraState.m_viewportSize); + return WorldToScreen(worldPosition, CameraView(cameraState), CameraProjection(cameraState), cameraState.m_viewportSize); } - AZ::Vector3 ScreenNDCToWorld( - const AZ::Vector2& normalizedScreenPosition, const AZ::Matrix4x4& inverseCameraView, - const AZ::Matrix4x4& inverseCameraProjection) + 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 = normalizedScreenPosition * 2.0f - AZ::Vector2::CreateOne(); @@ -142,18 +139,19 @@ namespace AzFramework } AZ::Vector3 ScreenToWorld( - const ScreenPoint& screenPosition, const AZ::Matrix4x4& inverseCameraView, - const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize) + const ScreenPoint& screenPosition, + const AZ::Matrix4x4& inverseCameraView, + const AZ::Matrix4x4& inverseCameraProjection, + const AZ::Vector2& viewportSize) { - const auto normalizedScreenPosition = NDCFromScreenPoint(screenPosition, viewportSize); + const auto normalizedScreenPosition = NdcFromScreenPoint(screenPosition, viewportSize); - return ScreenNDCToWorld(normalizedScreenPosition, inverseCameraView, inverseCameraProjection); + return ScreenNdcToWorld(normalizedScreenPosition, inverseCameraView, inverseCameraProjection); } AZ::Vector3 ScreenToWorld(const ScreenPoint& screenPosition, const CameraState& cameraState) { return ScreenToWorld( - screenPosition, InverseCameraView(cameraState), InverseCameraProjection(cameraState), - cameraState.m_viewportSize); + screenPosition, InverseCameraView(cameraState), InverseCameraProjection(cameraState), cameraState.m_viewportSize); } } // namespace AzFramework diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h index 87bad66eab..8a6c0249b2 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/ViewportScreen.h @@ -8,26 +8,42 @@ #pragma once +#include #include +#include +#include namespace AZ { class Frustum; class Matrix4x4; - class Vector3; struct ViewFrustumAttributes; } // namespace AZ namespace AzFramework { struct CameraState; - struct ScreenPoint; struct ViewportInfo; - //! Projects a position in world space to screen space normalized device coordinates for the given camera. - AZ::Vector3 WorldToScreenNDC( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection); + //! Returns a position in screen space (in the range [0-viewportSize.x, 0-viewportSize.y]) from normalized device + //! coordinates (in the range 0.0-1.0). + inline ScreenPoint ScreenPointFromNdc(const AZ::Vector2& screenNdc, const AZ::Vector2& viewportSize) + { + return ScreenPoint( + aznumeric_cast(AZStd::lround(screenNdc.GetX() * viewportSize.GetX())), + aznumeric_cast(AZStd::lround((1.0f - screenNdc.GetY()) * viewportSize.GetY()))); + } + //! Returns a position in normalized device coordinates (in the range [0.0-1.0, 0.0-1.0]) from a + //! screen space position (in the range [0-viewportSize.x, 0-viewportSize.y]). + inline AZ::Vector2 NdcFromScreenPoint(const ScreenPoint& screenPoint, const AZ::Vector2& viewportSize) + { + return AZ::Vector2(aznumeric_cast(screenPoint.m_x), viewportSize.GetY() - aznumeric_cast(screenPoint.m_y)) / + viewportSize; + } + + //! Projects a position in world space to screen space normalized device coordinates for the given camera. + AZ::Vector3 WorldToScreenNdc(const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection); //! Projects a position in world space to screen space for the given camera. ScreenPoint WorldToScreen(const AZ::Vector3& worldPosition, const CameraState& cameraState); @@ -35,7 +51,9 @@ namespace AzFramework //! Overload of WorldToScreen that accepts camera values that can be precomputed if this function //! is called many times in a loop. ScreenPoint WorldToScreen( - const AZ::Vector3& worldPosition, const AZ::Matrix4x4& cameraView, const AZ::Matrix4x4& cameraProjection, + const AZ::Vector3& worldPosition, + const AZ::Matrix4x4& cameraView, + const AZ::Matrix4x4& cameraProjection, const AZ::Vector2& viewportSize); //! Unprojects a position in screen space pixel coordinates to world space. @@ -45,14 +63,15 @@ namespace AzFramework //! Overload of ScreenToWorld that accepts camera values that can be precomputed if this function //! is called many times in a loop. AZ::Vector3 ScreenToWorld( - const ScreenPoint& screenPosition, const AZ::Matrix4x4& inverseCameraView, - const AZ::Matrix4x4& inverseCameraProjection, const AZ::Vector2& viewportSize); + 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); + 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); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp index 1867f346ec..d8c43ace9e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/ScaleManipulators.cpp @@ -71,6 +71,7 @@ namespace AzToolsFramework } m_uniformScaleManipulator->SetVisualOrientationOverride(QuaternionFromTransformNoScaling(localTransform)); + m_uniformScaleManipulator->SetLocalPosition(localTransform.GetTranslation()); m_uniformScaleManipulator->SetLocalOrientation(AZ::Quaternion::CreateIdentity()); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp index 049cab4a13..0efe310250 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Manipulators/TranslationManipulators.cpp @@ -13,14 +13,14 @@ namespace AzToolsFramework { - static const float s_surfaceManipulatorTransparency = 0.75f; - static const float s_axisLength = 2.0f; - static const float s_surfaceManipulatorRadius = 0.1f; + static const float SurfaceManipulatorTransparency = 0.75f; + static const float LinearManipulatorAxisLength = 2.0f; + static const float SurfaceManipulatorRadius = 0.1f; - static const AZ::Color s_xAxisColor = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f); - static const AZ::Color s_yAxisColor = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f); - static const AZ::Color s_zAxisColor = AZ::Color(0.0f, 0.0f, 1.0f, 1.0f); - static const AZ::Color s_surfaceManipulatorColor = AZ::Color(1.0f, 1.0f, 0.0f, 0.5f); + static const AZ::Color LinearManipulatorXAxisColor = AZ::Color(1.0f, 0.0f, 0.0f, 1.0f); + static const AZ::Color LinearManipulatorYAxisColor = AZ::Color(0.0f, 1.0f, 0.0f, 1.0f); + static const AZ::Color LinearManipulatorZAxisColor = AZ::Color(0.0f, 0.0f, 1.0f, 1.0f); + static const AZ::Color SurfaceManipulatorColor = AZ::Color(1.0f, 1.0f, 0.0f, 0.5f); TranslationManipulators::TranslationManipulators( const Dimensions dimensions, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale) @@ -291,7 +291,7 @@ namespace AzToolsFramework { const AZ::Color color[2] = { defaultColor, - Vector3ToVector4(BaseManipulator::s_defaultMouseOverColor.GetAsVector3(), s_surfaceManipulatorTransparency) + Vector3ToVector4(BaseManipulator::s_defaultMouseOverColor.GetAsVector3(), SurfaceManipulatorTransparency) }; return color[mouseOver]; @@ -325,15 +325,16 @@ namespace AzToolsFramework void ConfigureTranslationManipulatorAppearance3d(TranslationManipulators* translationManipulators) { translationManipulators->SetAxes(AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ()); - translationManipulators->ConfigurePlanarView(s_xAxisColor, s_yAxisColor, s_zAxisColor); - translationManipulators->ConfigureLinearView(s_axisLength, s_xAxisColor, s_yAxisColor, s_zAxisColor); - translationManipulators->ConfigureSurfaceView(s_surfaceManipulatorRadius, s_surfaceManipulatorColor); + translationManipulators->ConfigurePlanarView(LinearManipulatorXAxisColor, LinearManipulatorYAxisColor, LinearManipulatorZAxisColor); + translationManipulators->ConfigureLinearView( + LinearManipulatorAxisLength, LinearManipulatorXAxisColor, LinearManipulatorYAxisColor, LinearManipulatorZAxisColor); + translationManipulators->ConfigureSurfaceView(SurfaceManipulatorRadius, SurfaceManipulatorColor); } void ConfigureTranslationManipulatorAppearance2d(TranslationManipulators* translationManipulators) { translationManipulators->SetAxes(AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY()); - translationManipulators->ConfigurePlanarView(s_xAxisColor); - translationManipulators->ConfigureLinearView(s_axisLength, s_xAxisColor, s_yAxisColor); + translationManipulators->ConfigurePlanarView(LinearManipulatorXAxisColor); + translationManipulators->ConfigureLinearView(LinearManipulatorAxisLength, LinearManipulatorXAxisColor, LinearManipulatorYAxisColor); } } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 8157d369d2..ccb72e1d50 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -700,13 +700,6 @@ namespace AzToolsFramework switch (referenceFrame) { case ReferenceFrame::Local: - // if we have a group selection, always use the pivot override if one - // is set when moving to local space (can't pick individual local space) - if (entityIdMap.size() > 1) - { - pivot.m_worldOrientation = pivotOverrideFrame.m_orientationOverride.value(); - } - break; case ReferenceFrame::Parent: pivot.m_worldOrientation = pivotOverrideFrame.m_orientationOverride.value(); break; @@ -784,7 +777,7 @@ namespace AzToolsFramework SortEntitiesByLocationInHierarchy(sortedEntityIdsOut); } - static void UpdateInitialRotation(EntityIdManipulators& entityManipulators) + static void UpdateInitialTransform(EntityIdManipulators& entityManipulators) { // save new start orientation (if moving rotation axes separate from object // or switching type of rotation (modifier keys change)) @@ -797,22 +790,17 @@ namespace AzToolsFramework } } - // utility function to immediately return the current reference frame - // based on the state of the modifiers + // utility function to immediately return the current reference frame based on the state of the modifiers static ReferenceFrame ReferenceFrameFromModifiers(const ViewportInteraction::KeyboardModifiers modifiers) { - if (modifiers.Shift() && !modifiers.Alt()) - { - return ReferenceFrame::World; - } - else if (modifiers.Alt() && !modifiers.Shift()) - { - return ReferenceFrame::Local; - } - else - { - return ReferenceFrame::Parent; - } + return modifiers.Shift() ? ReferenceFrame::World : ReferenceFrame::Local; + } + + // utility function to immediately return the current sphere of influence of the manipulators based on the + // state of the modifiers + static Influence InfluenceFromModifiers(const ViewportInteraction::KeyboardModifiers modifiers) + { + return modifiers.Alt() ? Influence::Individual : Influence::Group; } template @@ -838,6 +826,7 @@ namespace AzToolsFramework else { const ReferenceFrame referenceFrame = spaceLock.value_or(ReferenceFrameFromModifiers(action.m_modifiers)); + const Influence influence = InfluenceFromModifiers(action.m_modifiers); // note: used for parent and world depending on the current reference frame const auto pivotOrientation = @@ -854,9 +843,9 @@ namespace AzToolsFramework const AZ::Vector3 worldTranslation = GetWorldTranslation(entityId); - switch (referenceFrame) + switch (influence) { - case ReferenceFrame::Local: + case Influence::Individual: { // move in each entities local space at once AZ::Quaternion worldOrientation = AZ::Quaternion::CreateIdentity(); @@ -876,10 +865,9 @@ namespace AzToolsFramework entityId, entityItLookupIt->second.m_initial.GetTranslation() + localOffset, transformChangedInternally); } break; - case ReferenceFrame::Parent: - case ReferenceFrame::World: + case Influence::Group: { - AZ::Quaternion offsetRotation = pivotOrientation.m_worldOrientation * + const AZ::Quaternion offsetRotation = pivotOrientation.m_worldOrientation * QuaternionFromTransformNoScaling(entityIdManipulators.m_manipulators->GetLocalTransform().GetInverse()); const AZ::Vector3 localOffset = offsetRotation.TransformVector(action.LocalPositionOffset()); @@ -1062,7 +1050,8 @@ namespace AzToolsFramework { AZStd::chrono::milliseconds timeNow; AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::BroadcastResult( - timeNow, &AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Events::EditorViewportInputTimeNow); + timeNow, + &AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Events::EditorViewportInputTimeNow); return timeNow; }); } @@ -1263,7 +1252,7 @@ namespace AzToolsFramework AZStd::unique_ptr translationManipulators = AZStd::make_unique( TranslationManipulators::Dimensions::Three, AZ::Transform::CreateIdentity(), AZ::Vector3::CreateOne()); - translationManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth(ViewportUi::DefaultViewportId)); + translationManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth()); InitializeManipulators(*translationManipulators); @@ -1297,7 +1286,7 @@ namespace AzToolsFramework ViewportInteraction::KeyboardModifiers prevModifiers{}; translationManipulators->InstallLinearManipulatorMouseMoveCallback( - [this, prevModifiers, manipulatorEntityIds](const LinearManipulator::Action& action) mutable -> void + [this, prevModifiers, manipulatorEntityIds](const LinearManipulator::Action& action) mutable { UpdateTranslationManipulator( action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers, @@ -1331,7 +1320,7 @@ namespace AzToolsFramework }); translationManipulators->InstallPlanarManipulatorMouseMoveCallback( - [this, prevModifiers, manipulatorEntityIds](const PlanarManipulator::Action& action) mutable -> void + [this, prevModifiers, manipulatorEntityIds](const PlanarManipulator::Action& action) mutable { UpdateTranslationManipulator( action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers, @@ -1364,7 +1353,7 @@ namespace AzToolsFramework }); translationManipulators->InstallSurfaceManipulatorMouseMoveCallback( - [this, prevModifiers, manipulatorEntityIds](const SurfaceManipulator::Action& action) mutable -> void + [this, prevModifiers, manipulatorEntityIds](const SurfaceManipulator::Action& action) mutable { UpdateTranslationManipulator( action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers, @@ -1391,7 +1380,7 @@ namespace AzToolsFramework AZStd::unique_ptr rotationManipulators = AZStd::make_unique(AZ::Transform::CreateIdentity()); - rotationManipulators->SetCircleBoundWidth(ManipulatorCicleBoundWidth(ViewportUi::DefaultViewportId)); + rotationManipulators->SetCircleBoundWidth(ManipulatorCicleBoundWidth()); InitializeManipulators(*rotationManipulators); @@ -1415,7 +1404,7 @@ namespace AzToolsFramework AZStd::shared_ptr sharedRotationState = AZStd::make_shared(); rotationManipulators->InstallLeftMouseDownCallback( - [this, sharedRotationState]([[maybe_unused]] const AngularManipulator::Action& action) mutable -> void + [this, sharedRotationState]([[maybe_unused]] const AngularManipulator::Action& action) mutable { sharedRotationState->m_savedOrientation = AZ::Quaternion::CreateIdentity(); sharedRotationState->m_referenceFrameAtMouseDown = m_referenceFrame; @@ -1437,11 +1426,13 @@ namespace AzToolsFramework BeginRecordManipulatorCommand(); }); - ViewportInteraction::KeyboardModifiers prevModifiers{}; rotationManipulators->InstallMouseMoveCallback( - [this, prevModifiers, sharedRotationState](const AngularManipulator::Action& action) mutable -> void + [this, prevModifiers = ViewportInteraction::KeyboardModifiers(), + sharedRotationState](const AngularManipulator::Action& action) mutable { const ReferenceFrame referenceFrame = m_spaceCluster.m_spaceLock.value_or(ReferenceFrameFromModifiers(action.m_modifiers)); + const Influence influence = InfluenceFromModifiers(action.m_modifiers); + const AZ::Quaternion manipulatorOrientation = action.m_start.m_rotation * action.m_current.m_delta; // store the pivot override frame when positioning the manipulator manually (ctrl) // so we don't lose the orientation when adding/removing entities from the selection @@ -1452,9 +1443,7 @@ namespace AzToolsFramework // only update the manipulator orientation if we're rotating in a local reference frame or we're // manually modifying the manipulator orientation independent of the entity by holding ctrl - if ((sharedRotationState->m_referenceFrameAtMouseDown == ReferenceFrame::Local && - m_entityIdManipulators.m_lookups.size() == 1) || - action.m_modifiers.Ctrl()) + if (sharedRotationState->m_referenceFrameAtMouseDown == ReferenceFrame::Local || action.m_modifiers.Ctrl()) { m_entityIdManipulators.m_manipulators->SetLocalTransform(AZ::Transform::CreateFromQuaternionAndTranslation( manipulatorOrientation, m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation())); @@ -1463,20 +1452,24 @@ namespace AzToolsFramework // save state if we change the type of rotation we're doing to to prevent snapping if (prevModifiers != action.m_modifiers) { - UpdateInitialRotation(m_entityIdManipulators); + UpdateInitialTransform(m_entityIdManipulators); sharedRotationState->m_savedOrientation = action.m_current.m_delta.GetInverseFull(); } // allow the user to modify the orientation without moving the object if ctrl is held if (action.m_modifiers.Ctrl()) { - UpdateInitialRotation(m_entityIdManipulators); + UpdateInitialTransform(m_entityIdManipulators); sharedRotationState->m_savedOrientation = action.m_current.m_delta.GetInverseFull(); } else { - const auto pivotOrientation = ETCS::CalculateSelectionPivotOrientation( - m_entityIdManipulators.m_lookups, m_pivotOverrideFrame, ReferenceFrame::Parent); + // only update the pivot override if the orientation is being modified in local space and we have + // more than one entity selected (so rotating a single entity does not set the orientation override) + if (referenceFrame == ReferenceFrame::Local && sharedRotationState->m_entityIds.size() > 1) + { + m_pivotOverrideFrame.m_orientationOverride = manipulatorOrientation; + } // note: must use sorted entityIds based on hierarchy order when updating transforms for (AZ::EntityId entityId : sharedRotationState->m_entityIds) @@ -1492,9 +1485,9 @@ namespace AzToolsFramework const AZ::Transform offsetRotation = AZ::Transform::CreateFromQuaternion(sharedRotationState->m_savedOrientation * action.m_current.m_delta); - switch (referenceFrame) + switch (influence) { - case ReferenceFrame::Local: + case Influence::Individual: { const AZ::Quaternion rotation = entityIdLookupIt->second.m_initial.GetRotation().GetNormalized(); const AZ::Vector3 position = entityIdLookupIt->second.m_initial.GetTranslation(); @@ -1510,23 +1503,10 @@ namespace AzToolsFramework AZ::Transform::CreateTranslation(-centerOffset) * AZ::Transform::CreateUniformScale(scale)); } break; - case ReferenceFrame::Parent: + case Influence::Group: { const AZ::Transform pivotTransform = AZ::Transform::CreateFromQuaternionAndTranslation( - pivotOrientation.m_worldOrientation, - m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation()); - - const AZ::Transform transformInPivotSpace = - pivotTransform.GetInverse() * entityIdLookupIt->second.m_initial; - - SetEntityWorldTransform(entityId, pivotTransform * offsetRotation * transformInPivotSpace); - } - break; - case ReferenceFrame::World: - { - const AZ::Transform pivotTransform = AZ::Transform::CreateFromQuaternionAndTranslation( - AZ::Quaternion::CreateIdentity(), - m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation()); + manipulatorOrientation, m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation()); const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * entityIdLookupIt->second.m_initial; @@ -1561,7 +1541,7 @@ namespace AzToolsFramework AZ_PROFILE_FUNCTION(AzToolsFramework); AZStd::unique_ptr scaleManipulators = AZStd::make_unique(AZ::Transform::CreateIdentity()); - scaleManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth(ViewportUi::DefaultViewportId)); + scaleManipulators->SetLineBoundWidth(ManipulatorLineBoundWidth()); InitializeManipulators(*scaleManipulators); @@ -1571,13 +1551,20 @@ namespace AzToolsFramework scaleManipulators->SetAxes(AZ::Vector3::CreateAxisX(), AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ()); scaleManipulators->ConfigureView(2.0f, AZ::Color::CreateOne(), AZ::Color::CreateOne(), AZ::Color::CreateOne()); - // lambdas capture shared_ptr by value to increment ref count - auto manipulatorEntityIds = AZStd::make_shared(); - - auto uniformLeftMouseDownCallback = [this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action) + struct SharedScaleState { + AZ::Vector3 m_savedScaleOffset = AZ::Vector3::CreateZero(); + EntityIdList m_entityIds; + }; + + // lambdas capture shared_ptr by value to increment ref count + auto sharedScaleState = AZStd::make_shared(); + + auto uniformLeftMouseDownCallback = [this, sharedScaleState]([[maybe_unused]] const LinearManipulator::Action& action) + { + sharedScaleState->m_savedScaleOffset = AZ::Vector3::CreateZero(); // important to sort entityIds based on hierarchy order when updating transforms - BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds); + BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, sharedScaleState->m_entityIds); for (auto& entityIdLookup : m_entityIdManipulators.m_lookups) { @@ -1591,20 +1578,32 @@ namespace AzToolsFramework m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); }; - auto uniformLeftMouseUpCallback = [this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action) + auto uniformLeftMouseUpCallback = [this, sharedScaleState]([[maybe_unused]] const LinearManipulator::Action& action) { AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast( - &AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged, - manipulatorEntityIds->m_entityIds); + &AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged, sharedScaleState->m_entityIds); m_entityIdManipulators.m_manipulators->SetLocalTransform(RecalculateAverageManipulatorTransform( m_entityIdManipulators.m_lookups, m_pivotOverrideFrame, m_pivotMode, m_referenceFrame)); }; - auto uniformLeftMouseMoveCallback = [this, manipulatorEntityIds](const LinearManipulator::Action& action) + auto uniformLeftMouseMoveCallback = [this, sharedScaleState, prevModifiers = ViewportInteraction::KeyboardModifiers()]( + const LinearManipulator::Action& action) mutable { + // do nothing to modify the manipulator + if (action.m_modifiers.Ctrl()) + { + return; + } + + if (prevModifiers != action.m_modifiers) + { + UpdateInitialTransform(m_entityIdManipulators); + sharedScaleState->m_savedScaleOffset = action.LocalScaleOffset(); + } + // note: must use sorted entityIds based on hierarchy order when updating transforms - for (AZ::EntityId entityId : manipulatorEntityIds->m_entityIds) + for (AZ::EntityId entityId : sharedScaleState->m_entityIds) { auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(entityId); if (entityIdLookupIt == m_entityIdManipulators.m_lookups.end()) @@ -1620,26 +1619,33 @@ namespace AzToolsFramework return vec.GetX() + vec.GetY() + vec.GetZ(); }; - const float uniformScale = action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset()); + const float uniformScale = + action.m_start.m_sign * sumVectorElements(action.LocalScaleOffset() - sharedScaleState->m_savedScaleOffset); const float scale = AZ::GetClamp(1.0f + uniformScale / initialScale, AZ::MinTransformScale, AZ::MaxTransformScale); const AZ::Transform scaleTransform = AZ::Transform::CreateUniformScale(scale); - if (action.m_modifiers.Alt()) + switch (InfluenceFromModifiers(action.m_modifiers)) { - const AZ::Transform pivotTransform = TransformNormalizedScale(entityIdLookupIt->second.m_initial); - const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; + case Influence::Individual: + { + const AZ::Transform pivotTransform = TransformNormalizedScale(entityIdLookupIt->second.m_initial); + const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; - SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); - } - else - { - const AZ::Transform pivotTransform = - TransformNormalizedScale(m_entityIdManipulators.m_manipulators->GetLocalTransform()); - const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; + SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); + } + break; + case Influence::Group: + { + const AZ::Transform pivotTransform = + TransformNormalizedScale(m_entityIdManipulators.m_manipulators->GetLocalTransform()); + const AZ::Transform transformInPivotSpace = pivotTransform.GetInverse() * initial; - SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); + SetEntityWorldTransform(entityId, pivotTransform * scaleTransform * transformInPivotSpace); + } } } + + prevModifiers = action.m_modifiers; }; scaleManipulators->InstallAxisLeftMouseDownCallback(uniformLeftMouseDownCallback); @@ -2729,8 +2735,7 @@ namespace AzToolsFramework if (m_pivotOverrideFrame.m_orientationOverride && m_entityIdManipulators.m_manipulators) { - m_pivotOverrideFrame.m_orientationOverride = - QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform()); + m_pivotOverrideFrame.m_orientationOverride = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetRotation(); } if (m_pivotOverrideFrame.m_translationOverride && m_entityIdManipulators.m_manipulators) @@ -3337,7 +3342,7 @@ namespace AzToolsFramework display.SetLineWidth(4.0f); - const auto axisFlip = [&transform, &cameraState](const AZ::Vector3& axis) -> float + const auto axisFlip = [&transform, &cameraState](const AZ::Vector3& axis) { return ShouldFlipCameraAxis( AZ::Transform::CreateIdentity(), transform.GetTranslation(), TransformDirectionNoScaling(transform, axis), @@ -3554,7 +3559,7 @@ namespace AzToolsFramework // screen space const auto calculateGizmoAxis = [&cameraView, &cameraProjection, &screenOffset](const AZ::Vector3& axis) { - auto result = AZ::Vector2(AzFramework::WorldToScreenNDC(axis, cameraView, cameraProjection)); + auto result = AZ::Vector2(AzFramework::WorldToScreenNdc(axis, cameraView, cameraProjection)); result.SetY(1.0f - result.GetY()); return result + screenOffset; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 6cc89b2d10..a0e87d9d6f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -96,6 +96,14 @@ namespace AzToolsFramework AZ::u8 m_pickTypes = PickType::None; //!< What mode(s) were we in when picking an EntityId override. }; + //! How a manipulator should treat an adjustment. + //! @note Determines if a transform is applied to an individual entity or the whole group. + enum class Influence + { + Group, + Individual + }; + //! What frame/space is the manipulator currently operating in. enum class ReferenceFrame { @@ -328,7 +336,8 @@ namespace AzToolsFramework OptionalFrame m_pivotOverrideFrame; //!< Has a pivot override been set. Mode m_mode = Mode::Translation; //!< Manipulator mode - default to translation. Pivot m_pivotMode = Pivot::Object; //!< Entity pivot mode - default to object (authored root). - ReferenceFrame m_referenceFrame = ReferenceFrame::Parent; //!< What reference frame is the Manipulator currently operating in. + ReferenceFrame m_referenceFrame = ReferenceFrame::Local; //!< What reference frame is the Manipulator currently operating in. + Influence m_influence = Influence::Group; //!< What sphere of influence does the Manipulator have. Frame m_axisPreview; //!< Axes of entity at the time of mouse down to indicate delta of translation. bool m_triedToRefresh = false; //!< Did a refresh event occur to recalculate the current Manipulator transform. //! Was EditorTransformComponentSelection responsible for the most recent entity selection change. diff --git a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp index 449c4549be..52470f41e3 100644 --- a/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/EditorTransformComponentSelectionTests.cpp @@ -242,11 +242,11 @@ namespace UnitTest { // the initial starting position of the entities AZ::TransformBus::Event( - m_entityId1, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(m_entity1WorldTranslation)); + m_entityId1, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(Entity1WorldTranslation)); AZ::TransformBus::Event( - m_entityId2, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(m_entity2WorldTranslation)); + m_entityId2, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(Entity2WorldTranslation)); AZ::TransformBus::Event( - m_entityId3, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(m_entity3WorldTranslation)); + m_entityId3, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(Entity3WorldTranslation)); } static void PositionCamera(AzFramework::CameraState& cameraState) @@ -261,9 +261,10 @@ namespace UnitTest AZ::EntityId m_entityId1; AZ::EntityId m_entityId2; AZ::EntityId m_entityId3; - AZ::Vector3 m_entity1WorldTranslation = AZ::Vector3(5.0f, 15.0f, 10.0f); - AZ::Vector3 m_entity2WorldTranslation = AZ::Vector3(5.0f, 14.0f, 10.0f); - AZ::Vector3 m_entity3WorldTranslation = AZ::Vector3(5.0f, 16.0f, 10.0f); + + static inline const AZ::Vector3 Entity1WorldTranslation = AZ::Vector3(5.0f, 15.0f, 10.0f); + static inline const AZ::Vector3 Entity2WorldTranslation = AZ::Vector3(5.0f, 14.0f, 10.0f); + static inline const AZ::Vector3 Entity3WorldTranslation = AZ::Vector3(5.0f, 16.0f, 10.0f); }; void ArrangeIndividualRotatedEntitySelection(const AzToolsFramework::EntityIdList& entityIds, const AZ::Quaternion& orientation) @@ -371,16 +372,16 @@ namespace UnitTest // Given AzToolsFramework::SelectEntity(m_entityId1); - ArrangeIndividualRotatedEntitySelection(m_entityIds, AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f))); + const auto entityTransform = AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f))); + ArrangeIndividualRotatedEntitySelection(m_entityIds, entityTransform.GetRotation()); RefreshManipulators(EditorTransformComponentSelectionRequestBus::Events::RefreshType::All); SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Rotation); const AZ::Transform manipulatorTransformBefore = GetManipulatorTransform().value_or(AZ::Transform::CreateIdentity()); - // check preconditions - manipulator transform matches parent/world transform (identity) - EXPECT_THAT(manipulatorTransformBefore.GetBasisY(), IsClose(AZ::Vector3::CreateAxisY())); - EXPECT_THAT(manipulatorTransformBefore.GetBasisZ(), IsClose(AZ::Vector3::CreateAxisZ())); + // check preconditions - manipulator transform matches the entity transform + EXPECT_THAT(manipulatorTransformBefore, IsClose(entityTransform)); /////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -624,7 +625,7 @@ namespace UnitTest EXPECT_TRUE(selectedEntitiesBefore.empty()); // calculate the position in screen space of the initial entity position - const auto entity1ScreenPosition = AzFramework::WorldToScreen(m_entity1WorldTranslation, m_cameraState); + const auto entity1ScreenPosition = AzFramework::WorldToScreen(Entity1WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -649,7 +650,7 @@ namespace UnitTest EXPECT_TRUE(selectedEntitiesBefore.empty()); // calculate the position in screen space of the initial entity position - const auto entity1ScreenPosition = AzFramework::WorldToScreen(m_entity1WorldTranslation, m_cameraState); + const auto entity1ScreenPosition = AzFramework::WorldToScreen(Entity1WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -728,7 +729,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -754,7 +755,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -780,7 +781,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -806,7 +807,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -832,7 +833,7 @@ namespace UnitTest AzToolsFramework::SelectEntities({ m_entityId1, m_entityId2 }); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(true) @@ -858,7 +859,7 @@ namespace UnitTest AzToolsFramework::SelectEntities({ m_entityId1, m_entityId2 }); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // click the entity in the viewport m_actionDispatcher->SetStickySelect(false) @@ -1001,7 +1002,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // single click select entity2 m_actionDispatcher->SetStickySelect(false) @@ -1035,7 +1036,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // single click select entity2 m_actionDispatcher->SetStickySelect(GetParam()) @@ -1056,7 +1057,7 @@ namespace UnitTest manipulatorTransform, AzToolsFramework::GetEntityContextId(), &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation)); + EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(Entity2WorldTranslation)); } TEST_P( @@ -1069,7 +1070,7 @@ namespace UnitTest AzToolsFramework::SelectEntity(m_entityId1); // calculate the position in screen space of the second entity - const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState); + const auto entity2ScreenPosition = AzFramework::WorldToScreen(Entity2WorldTranslation, m_cameraState); // position in space above the entities const auto clickOffPositionWorld = AZ::Vector3(5.0f, 15.0f, 12.0f); @@ -1096,7 +1097,7 @@ namespace UnitTest manipulatorTransform, AzToolsFramework::GetEntityContextId(), &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation)); + EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(Entity2WorldTranslation)); }) ->MousePosition(clickOffPositionScreen) ->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control) @@ -1113,11 +1114,560 @@ namespace UnitTest &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); // manipulator transform is reset - EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity1WorldTranslation)); + EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(Entity1WorldTranslation)); } INSTANTIATE_TEST_CASE_P(All, EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam, testing::Values(true, false)); + // create alias for EditorTransformComponentSelectionViewportPickingManipulatorTestFixture to help group tests + using EditorTransformComponentSelectionManipulatorInteractionTestFixture = + EditorTransformComponentSelectionViewportPickingManipulatorTestFixture; + + // type to group related inputs and outcomes for parameterized tests (single entity) + struct ManipulatorOptionsSingle + { + AzToolsFramework::ViewportInteraction::KeyboardModifier m_keyboardModifier; + AZ::Transform m_expectedManipulatorTransformAfter; + AZ::Transform m_expectedEntityTransformAfter; + }; + + class EditorTransformComponentSelectionRotationManipulatorSingleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + TEST_P( + EditorTransformComponentSelectionRotationManipulatorSingleEntityTestFixtureParam, + RotatingASingleEntityWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + PositionCamera(m_cameraState); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Rotation); + + AzToolsFramework::SelectEntity(m_entityId1); + + const float screenToWorldMultiplier = AzToolsFramework::CalculateScreenToWorldMultiplier(Entity1WorldTranslation, m_cameraState); + const float manipulatorRadius = 2.0f * screenToWorldMultiplier; + + const auto rotationManipulatorStartHoldWorldPosition = Entity1WorldTranslation + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-45.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + const auto rotationManipulatorEndHoldWorldPosition = Entity1WorldTranslation + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-135.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + + // calculate screen space positions + const auto rotationManipulatorHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorStartHoldWorldPosition, m_cameraState); + const auto rotationManipulatorEndHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorEndHoldWorldPosition, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(rotationManipulatorHoldScreenPosition) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(rotationManipulatorEndHoldScreenPosition) + ->MouseLButtonUp(); + + const auto expectedEntityTransform = GetParam().m_expectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransform = GetManipulatorTransform(); + const auto entityTransform = AzToolsFramework::GetWorldTransform(m_entityId1); + + EXPECT_THAT(*manipulatorTransform, IsClose(expectedManipulatorTransform)); + EXPECT_THAT(entityTransform, IsClose(expectedEntityTransform)); + } + + static const AZ::Transform ExpectedTransformAfterLocalRotationManipulatorMotion = AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f)), + EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionRotationManipulatorSingleEntityTestFixtureParam, + testing::Values( + // this replicates rotating an entity in local space with no modifiers held + // manipulator and entity rotate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedTransformAfterLocalRotationManipulatorMotion }, + // this replicates rotating an entity in local space with the alt modifier held + // manipulator and entity rotate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedTransformAfterLocalRotationManipulatorMotion }, + // this replicates rotating an entity in world space with the shift modifier held + // entity rotates, manipulator remains aligned to world + ManipulatorOptionsSingle{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation), + ExpectedTransformAfterLocalRotationManipulatorMotion }, + // this replicates rotating the manipulator in local space with the ctrl modifier held (entity is unchanged) + ManipulatorOptionsSingle{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, ExpectedTransformAfterLocalRotationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation) })); + + // type to group related inputs and outcomes for parameterized tests (two entities) + struct ManipulatorOptionsMultiple + { + AzToolsFramework::ViewportInteraction::KeyboardModifier m_keyboardModifier; + AZ::Transform m_expectedManipulatorTransformAfter; + AZ::Transform m_firstExpectedEntityTransformAfter; + AZ::Transform m_secondExpectedEntityTransformAfter; + }; + + class EditorTransformComponentSelectionRotationManipulatorMultipleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + TEST_P( + EditorTransformComponentSelectionRotationManipulatorMultipleEntityTestFixtureParam, + RotatingMultipleEntitiesWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + PositionCamera(m_cameraState); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Rotation); + + AzToolsFramework::SelectEntities({ m_entityId2, m_entityId3 }); + + // manipulator should be centered between the two entities + const auto initialManipulatorTransform = GetManipulatorTransform(); + + const float screenToWorldMultiplier = + AzToolsFramework::CalculateScreenToWorldMultiplier(initialManipulatorTransform->GetTranslation(), m_cameraState); + const float manipulatorRadius = 2.0f * screenToWorldMultiplier; + + const auto rotationManipulatorStartHoldWorldPosition = initialManipulatorTransform->GetTranslation() + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-45.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + const auto rotationManipulatorEndHoldWorldPosition = initialManipulatorTransform->GetTranslation() + + AZ::Quaternion::CreateRotationX(AZ::DegToRad(-135.0f)).TransformVector(AZ::Vector3::CreateAxisY(-manipulatorRadius)); + + // calculate screen space positions + const auto rotationManipulatorHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorStartHoldWorldPosition, m_cameraState); + const auto rotationManipulatorEndHoldScreenPosition = + AzFramework::WorldToScreen(rotationManipulatorEndHoldWorldPosition, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(rotationManipulatorHoldScreenPosition) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(rotationManipulatorEndHoldScreenPosition) + ->MouseLButtonUp(); + + const auto expectedEntity2Transform = GetParam().m_firstExpectedEntityTransformAfter; + const auto expectedEntity3Transform = GetParam().m_secondExpectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransformAfter = GetManipulatorTransform(); + const auto entity2Transform = AzToolsFramework::GetWorldTransform(m_entityId2); + const auto entity3Transform = AzToolsFramework::GetWorldTransform(m_entityId3); + + EXPECT_THAT(*manipulatorTransformAfter, IsClose(expectedManipulatorTransform)); + EXPECT_THAT(entity2Transform, IsClose(expectedEntity2Transform)); + EXPECT_THAT(entity3Transform, IsClose(expectedEntity3Transform)); + } + + // note: The aggregate manipulator position will be the average of entity 2 and 3 combined which + // winds up being the same as entity 1 + static const AZ::Vector3 AggregateManipulatorPositionWithEntity2and3Selected = + EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation; + + static const AZ::Transform ExpectedEntity2TransformAfterLocalGroupRotationManipulatorMotion = + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))) * + AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(-1.0f)); + static const AZ::Transform ExpectedEntity3TransformAfterLocalGroupRotationManipulatorMotion = + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))) * + AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(1.0f)); + static const AZ::Transform ExpectedEntity2TransformAfterLocalIndividualRotationManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))); + static const AZ::Transform ExpectedEntity3TransformAfterLocalIndividualRotationManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) * + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationX(AZ::DegToRad(-90.0f))); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionRotationManipulatorMultipleEntityTestFixtureParam, + testing::Values( + // this replicates rotating a group of entities in local space with no modifiers held + // manipulator and entity rotate + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedEntity2TransformAfterLocalGroupRotationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupRotationManipulatorMotion }, + // this replicates rotating a group of entities in local space with the alt modifier held + // manipulator and entity rotate + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedTransformAfterLocalRotationManipulatorMotion, + ExpectedEntity2TransformAfterLocalIndividualRotationManipulatorMotion, + ExpectedEntity3TransformAfterLocalIndividualRotationManipulatorMotion }, + // this replicates rotating a group of entities in world space with the shift modifier held + // entity rotates, manipulator remains aligned to world + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation), + ExpectedEntity2TransformAfterLocalGroupRotationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupRotationManipulatorMotion }, + // this replicates rotating the manipulator in local space with the ctrl modifier held (entity is unchanged) + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, ExpectedTransformAfterLocalRotationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation), + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) })); + + class EditorTransformComponentSelectionTranslationManipulatorSingleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + static const float LinearManipulatorYAxisMovement = -3.0f; + static const float LinearManipulatorZAxisMovement = 2.0f; + + TEST_P( + EditorTransformComponentSelectionTranslationManipulatorSingleEntityTestFixtureParam, + TranslatingASingleEntityWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + + // move camera up and to the left so it's just above the normal row of entities + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 14.5, 11.0f))); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Translation); + + AzToolsFramework::SelectEntity(m_entityId1); + const auto entity1Transform = AzToolsFramework::GetWorldTransform(m_entityId1); + + const float screenToWorldMultiplier = AzToolsFramework::CalculateScreenToWorldMultiplier( + AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation(), m_cameraState); + + // calculate positions for two click and drag motions (moving a linear manipulator) + // begin each click in the center of the line of the linear manipulators + const auto translationManipulatorStartHoldWorldPosition1 = + AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + entity1Transform.GetBasisZ() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition1 = + translationManipulatorStartHoldWorldPosition1 + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement); + const auto translationManipulatorStartHoldWorldPosition2 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement) - entity1Transform.GetBasisY() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition2 = + translationManipulatorStartHoldWorldPosition2 + AZ::Vector3::CreateAxisY(LinearManipulatorYAxisMovement); + + // transform to screen space + const auto translationManipulatorStartHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition1, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition1, m_cameraState); + const auto translationManipulatorStartHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition2, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition2, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(translationManipulatorStartHoldScreenPosition1) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition1) + ->MouseLButtonUp() + ->MousePosition(translationManipulatorStartHoldScreenPosition2) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition2) + ->MouseLButtonUp(); + + const auto expectedEntityTransform = GetParam().m_expectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransform = GetManipulatorTransform(); + const auto entityTransform = AzToolsFramework::GetWorldTransform(m_entityId1); + + EXPECT_THAT(*manipulatorTransform, IsCloseTolerance(expectedManipulatorTransform, 0.01f)); + EXPECT_THAT(entityTransform, IsCloseTolerance(expectedEntityTransform, 0.01f)); + } + + static const AZ::Transform ExpectedTransformAfterLocalTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + + // where the manipulator should end up after the input from TranslatingMultipleEntitiesWithDifferentModifierCombinations + static const AZ::Transform ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + AggregateManipulatorPositionWithEntity2and3Selected + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionTranslationManipulatorSingleEntityTestFixtureParam, + testing::Values( + // this replicates translating an entity in local space with no modifiers held + // manipulator and entity translate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedTransformAfterLocalTranslationManipulatorMotion, + ExpectedTransformAfterLocalTranslationManipulatorMotion }, + // this replicates translating an entity in local space with the alt modifier held + // manipulator and entity translate (to the user, equivalent to no modifiers with one entity selected) + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedTransformAfterLocalTranslationManipulatorMotion, + ExpectedTransformAfterLocalTranslationManipulatorMotion }, + // this replicates translating an entity in world space with the shift modifier held + // manipulator and entity translate + ManipulatorOptionsSingle{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + ExpectedTransformAfterLocalTranslationManipulatorMotion, + ExpectedTransformAfterLocalTranslationManipulatorMotion }, + // this replicates translating the manipulator in local space with the ctrl modifier held + // entity is unchanged, manipulator moves + ManipulatorOptionsSingle{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, ExpectedTransformAfterLocalTranslationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity1WorldTranslation) })); + + class EditorTransformComponentSelectionTranslationManipulatorMultipleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + static const AZ::Transform Entity2RotationForLocalTranslation = + AZ::Transform::CreateFromQuaternion(AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f))); + + TEST_P( + EditorTransformComponentSelectionTranslationManipulatorMultipleEntityTestFixtureParam, + TranslatingMultipleEntitiesWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + + // move camera up and to the left so it's just above the normal row of entities + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 14.5, 11.0f))); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Translation); + + // give entity 2 a different orientation to entity 3 so when moving in local space their translation vectors will be different + AZ::TransformBus::Event( + m_entityId2, &AZ::TransformBus::Events::SetWorldRotationQuaternion, Entity2RotationForLocalTranslation.GetRotation()); + + AzToolsFramework::SelectEntities({ m_entityId2, m_entityId3 }); + + const auto initialManipulatorTransform = GetManipulatorTransform(); + + const float screenToWorldMultiplier = AzToolsFramework::CalculateScreenToWorldMultiplier( + AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation(), m_cameraState); + + // calculate positions for two click and drag motions (moving a linear manipulator) + // begin each click in the center of the line of the linear manipulators + const auto translationManipulatorStartHoldWorldPosition1 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + initialManipulatorTransform->GetBasisZ() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition1 = + translationManipulatorStartHoldWorldPosition1 + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement); + const auto translationManipulatorStartHoldWorldPosition2 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovement) - initialManipulatorTransform->GetBasisY() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition2 = + translationManipulatorStartHoldWorldPosition2 + AZ::Vector3::CreateAxisY(LinearManipulatorYAxisMovement); + + // transform to screen space + const auto translationManipulatorStartHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition1, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition1 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition1, m_cameraState); + const auto translationManipulatorStartHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition2, m_cameraState); + const auto translationManipulatorEndHoldScreenPosition2 = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition2, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(translationManipulatorStartHoldScreenPosition1) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition1) + ->MouseLButtonUp() + ->MousePosition(translationManipulatorStartHoldScreenPosition2) + ->MouseLButtonDown() + ->MousePosition(translationManipulatorEndHoldScreenPosition2) + ->MouseLButtonUp(); + + const auto expectedEntity2Transform = GetParam().m_firstExpectedEntityTransformAfter; + const auto expectedEntity3Transform = GetParam().m_secondExpectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransformAfter = GetManipulatorTransform(); + const auto entity2Transform = AzToolsFramework::GetWorldTransform(m_entityId2); + const auto entity3Transform = AzToolsFramework::GetWorldTransform(m_entityId3); + + EXPECT_THAT(*manipulatorTransformAfter, IsCloseTolerance(expectedManipulatorTransform, 0.01f)); + EXPECT_THAT(entity2Transform, IsCloseTolerance(expectedEntity2Transform, 0.01f)); + EXPECT_THAT(entity3Transform, IsCloseTolerance(expectedEntity3Transform, 0.01f)); + } + + static const AZ::Transform ExpectedEntity2TransformAfterLocalGroupTranslationManipulatorMotion = + AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)) * + Entity2RotationForLocalTranslation; + static const AZ::Transform ExpectedEntity3TransformAfterLocalGroupTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + // note: as entity has been rotated by 90 degrees about Z in TranslatingMultipleEntitiesWithDifferentModifierCombinations then + // LinearManipulatorYAxisMovement is now aligned to the world x-axis + static const AZ::Transform ExpectedEntity2TransformAfterLocalIndividualTranslationManipulatorMotion = + AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation + + AZ::Vector3(-LinearManipulatorYAxisMovement, 0.0f, LinearManipulatorZAxisMovement)) * + Entity2RotationForLocalTranslation; + static const AZ::Transform ExpectedEntity3TransformAfterLocalIndividualTranslationManipulatorMotion = AZ::Transform::CreateTranslation( + EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation + + AZ::Vector3(0.0f, LinearManipulatorYAxisMovement, LinearManipulatorZAxisMovement)); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionTranslationManipulatorMultipleEntityTestFixtureParam, + testing::Values( + // this replicates translating a group of entities in local space with no modifiers held (group influence) + // manipulator and entity translate + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + ExpectedEntity2TransformAfterLocalGroupTranslationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupTranslationManipulatorMotion }, + // this replicates translating a group of entities in local space with the alt modifier held + // entities move in their own local space (individual influence) + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + ExpectedEntity2TransformAfterLocalIndividualTranslationManipulatorMotion, + ExpectedEntity3TransformAfterLocalIndividualTranslationManipulatorMotion }, + // this replicates translating a group of entities in world space with the shift modifier held + // entities and manipulator move in world space + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + ExpectedEntity2TransformAfterLocalGroupTranslationManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupTranslationManipulatorMotion }, + // this replicates translating the manipulator in local space with the ctrl modifier held (entities are unchanged) + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, + ExpectedManipulatorTransformAfterGroupTranslationManipulatorMotion, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + Entity2RotationForLocalTranslation, + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) })); + + class EditorTransformComponentSelectionScaleManipulatorMultipleEntityTestFixtureParam + : public EditorTransformComponentSelectionManipulatorInteractionTestFixture + , public ::testing::WithParamInterface + { + }; + + static const float LinearManipulatorZAxisMovementScale = 0.5f; + + TEST_P( + EditorTransformComponentSelectionScaleManipulatorMultipleEntityTestFixtureParam, + ScalingMultipleEntitiesWithDifferentModifierCombinations) + { + using AzToolsFramework::EditorTransformComponentSelectionRequestBus; + + PositionEntities(); + + // move camera up and to the left so it's just above the normal row of entities + AzFramework::SetCameraTransform( + m_cameraState, + AZ::Transform::CreateFromQuaternionAndTranslation( + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 15.0f, 10.1f))); + + SetTransformMode(EditorTransformComponentSelectionRequestBus::Events::Mode::Scale); + + AzToolsFramework::SelectEntities({ m_entityId2, m_entityId3 }); + + // manipulator should be centered between the two entities + const auto initialManipulatorTransform = GetManipulatorTransform(); + + const float screenToWorldMultiplier = + AzToolsFramework::CalculateScreenToWorldMultiplier(initialManipulatorTransform->GetTranslation(), m_cameraState); + + const auto translationManipulatorStartHoldWorldPosition1 = AzToolsFramework::GetWorldTransform(m_entityId1).GetTranslation() + + initialManipulatorTransform->GetBasisZ() * screenToWorldMultiplier; + const auto translationManipulatorEndHoldWorldPosition1 = + translationManipulatorStartHoldWorldPosition1 + AZ::Vector3::CreateAxisZ(LinearManipulatorZAxisMovementScale); + + // calculate screen space positions + const auto scaleManipulatorHoldScreenPosition = + AzFramework::WorldToScreen(translationManipulatorStartHoldWorldPosition1, m_cameraState); + const auto scaleManipulatorEndHoldScreenPosition = + AzFramework::WorldToScreen(translationManipulatorEndHoldWorldPosition1, m_cameraState); + + m_actionDispatcher->CameraState(m_cameraState) + ->MousePosition(scaleManipulatorHoldScreenPosition) + ->KeyboardModifierDown(GetParam().m_keyboardModifier) + ->MouseLButtonDown() + ->MousePosition(scaleManipulatorEndHoldScreenPosition) + ->MouseLButtonUp(); + + const auto expectedEntity2Transform = GetParam().m_firstExpectedEntityTransformAfter; + const auto expectedEntity3Transform = GetParam().m_secondExpectedEntityTransformAfter; + const auto expectedManipulatorTransform = GetParam().m_expectedManipulatorTransformAfter; + + const auto manipulatorTransformAfter = GetManipulatorTransform(); + const auto entity2Transform = AzToolsFramework::GetWorldTransform(m_entityId2); + const auto entity3Transform = AzToolsFramework::GetWorldTransform(m_entityId3); + + EXPECT_THAT(*manipulatorTransformAfter, IsCloseTolerance(expectedManipulatorTransform, 0.01f)); + EXPECT_THAT(entity2Transform, IsCloseTolerance(expectedEntity2Transform, 0.01f)); + EXPECT_THAT(entity3Transform, IsCloseTolerance(expectedEntity3Transform, 0.01f)); + } + + static const AZ::Transform ExpectedEntity2TransformAfterLocalGroupScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, -1.0f, 0.0f)) * + AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + static const AZ::Transform ExpectedEntity3TransformAfterLocalGroupScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) * + AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 1.0f, 0.0f)) * AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + static const AZ::Transform ExpectedEntity2TransformAfterLocalIndividualScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation) * + AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + static const AZ::Transform ExpectedEntity3TransformAfterLocalIndividualScaleManipulatorMotion = + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) * + AZ::Transform::CreateUniformScale(LinearManipulatorZAxisMovement); + + INSTANTIATE_TEST_CASE_P( + All, + EditorTransformComponentSelectionScaleManipulatorMultipleEntityTestFixtureParam, + testing::Values( + // this replicates scaling a group of entities in local space with no modifiers held + // entities scale relative to manipulator pivot + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::None, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + ExpectedEntity2TransformAfterLocalGroupScaleManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupScaleManipulatorMotion }, + // this replicates scaling a group of entities in local space with the alt modifier held + // entities scale about their own pivot + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + ExpectedEntity2TransformAfterLocalIndividualScaleManipulatorMotion, + ExpectedEntity3TransformAfterLocalIndividualScaleManipulatorMotion }, + // this replicates scaling a group of entities in world space with the shift modifier held + // entities scale relative to manipulator pivot in world space + ManipulatorOptionsMultiple{ AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + ExpectedEntity2TransformAfterLocalGroupScaleManipulatorMotion, + ExpectedEntity3TransformAfterLocalGroupScaleManipulatorMotion }, + // this has no effect (entities and manipulator are unchanged) + ManipulatorOptionsMultiple{ + AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl, + AZ::Transform::CreateTranslation(AggregateManipulatorPositionWithEntity2and3Selected), + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity2WorldTranslation), + AZ::Transform::CreateTranslation(EditorTransformComponentSelectionViewportPickingFixture::Entity3WorldTranslation) })); + using EditorTransformComponentSelectionManipulatorTestFixture = IndirectCallManipulatorViewportInteractionFixtureMixin; @@ -1661,7 +2211,7 @@ namespace UnitTest All, EditorTransformComponentSelectionSingleEntityPivotAndOverrideFixture, testing::Values( - ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::Local, ChildExpectedPivotLocalOrientationInWorldSpace }, + ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::Local, PivotOverrideLocalOrientationInWorldSpace }, ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::Parent, PivotOverrideLocalOrientationInWorldSpace }, ReferenceFrameWithOrientation{ AzToolsFramework::ReferenceFrame::World, AZ::Quaternion::CreateIdentity() })); diff --git a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp index 77058038e9..e8fce6653f 100644 --- a/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Viewport/ViewportScreenTests.cpp @@ -6,8 +6,8 @@ * */ -#include #include +#include #include #include #include @@ -20,18 +20,17 @@ 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) + // 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)); + 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) + AzFramework::ScreenPoint ScreenToWorldToScreen(const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState) { const auto worldResult = AzFramework::ScreenToWorld(screenPoint, cameraState); return AzFramework::WorldToScreen(worldResult, cameraState); @@ -47,25 +46,25 @@ namespace UnitTest const auto cameraState = AzFramework::CreateIdentityDefaultCamera(cameraPosition, screenDimensions); { - const auto expectedScreenPoint = ScreenPoint{600, 450}; + const auto expectedScreenPoint = ScreenPoint{ 600, 450 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } { - const auto expectedScreenPoint = ScreenPoint{400, 300}; + const auto expectedScreenPoint = ScreenPoint{ 400, 300 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } { - const auto expectedScreenPoint = ScreenPoint{0, 0}; + const auto expectedScreenPoint = ScreenPoint{ 0, 0 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } { - const auto expectedScreenPoint = ScreenPoint{800, 600}; + const auto expectedScreenPoint = ScreenPoint{ 800, 600 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } @@ -81,7 +80,7 @@ namespace UnitTest const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions); - const auto expectedScreenPoint = ScreenPoint{200, 300}; + const auto expectedScreenPoint = ScreenPoint{ 200, 300 }; const auto resultScreenPoint = ScreenToWorldToScreen(expectedScreenPoint, cameraState); EXPECT_EQ(resultScreenPoint, expectedScreenPoint); } @@ -93,46 +92,46 @@ namespace UnitTest using AzFramework::ScreenPoint; 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 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::ScreenToWorld(ScreenPoint{400, 300}, cameraState); + 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); + 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); + 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); + 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); + const auto expectedNdcPoint = NdcPoint{ 1.0f, 1.0f }; + const auto resultNdcPoint = ScreenNdcToWorldToScreenNdc(expectedNdcPoint, cameraState); EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint)); } } @@ -147,8 +146,8 @@ namespace UnitTest const auto cameraState = AzFramework::CreateDefaultCamera(cameraTransform, screenDimensions); - const auto expectedNdcPoint = NdcPoint{0.25f, 0.5f}; - const auto resultNdcPoint = ScreenNDCToWorldToScreenNDC(expectedNdcPoint, cameraState); + const auto expectedNdcPoint = NdcPoint{ 0.25f, 0.5f }; + const auto resultNdcPoint = ScreenNdcToWorldToScreenNdc(expectedNdcPoint, cameraState); EXPECT_THAT(resultNdcPoint, IsClose(expectedNdcPoint)); } @@ -159,12 +158,13 @@ namespace UnitTest 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 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)); + 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))); } @@ -175,7 +175,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenVector screenVector = ScreenPoint{100, 200} - ScreenPoint{10, 20}; + const ScreenVector screenVector = ScreenPoint{ 100, 200 } - ScreenPoint{ 10, 20 }; EXPECT_EQ(screenVector, ScreenVector(90, 180)); } @@ -184,7 +184,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenPoint screenPoint = ScreenPoint{100, 200} + ScreenVector{50, 25}; + const ScreenPoint screenPoint = ScreenPoint{ 100, 200 } + ScreenVector{ 50, 25 }; EXPECT_EQ(screenPoint, ScreenPoint(150, 225)); } @@ -193,7 +193,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenPoint screenPoint = ScreenPoint{120, 200} - ScreenVector{50, 20}; + const ScreenPoint screenPoint = ScreenPoint{ 120, 200 } - ScreenVector{ 50, 20 }; EXPECT_EQ(screenPoint, ScreenPoint(70, 180)); } @@ -202,7 +202,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenVector screenVector = ScreenVector{100, 200} + ScreenVector{50, 25}; + const ScreenVector screenVector = ScreenVector{ 100, 200 } + ScreenVector{ 50, 25 }; EXPECT_EQ(screenVector, ScreenVector(150, 225)); } @@ -211,7 +211,7 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenVector screenVector = ScreenVector{100, 200} - ScreenVector{50, 25}; + const ScreenVector screenVector = ScreenVector{ 100, 200 } - ScreenVector{ 50, 25 }; EXPECT_EQ(screenVector, ScreenVector(50, 175)); } @@ -220,8 +220,8 @@ namespace UnitTest using AzFramework::ScreenPoint; using AzFramework::ScreenVector; - const ScreenPoint screenPoint = ScreenPoint{100, 200}; - const ScreenVector screenVector = ScreenVector{50, 25}; + const ScreenPoint screenPoint = ScreenPoint{ 100, 200 }; + const ScreenVector screenVector = ScreenVector{ 50, 25 }; const AZ::Vector2 fromScreenPoint = AzFramework::Vector2FromScreenPoint(screenPoint); const AZ::Vector2 fromScreenVector = AzFramework::Vector2FromScreenVector(screenVector); @@ -295,6 +295,58 @@ namespace UnitTest EXPECT_NEAR(AzFramework::ScreenVectorLength(ScreenVector(12, 15)), 19.20937f, 0.001f); } + TEST(ViewportScreen, ScreenVectorTransformedByScalarUpwards) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(5, 10); + auto scaledScreenVector = screenVector * 2.0f; + + EXPECT_EQ(scaledScreenVector, ScreenVector(10, 20)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarWithRounding) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(1, 6); + auto scaledScreenVector = screenVector * 0.1f; + + // value less than 0.5 rounds down, greater than or equal to 0.5 rounds up + EXPECT_EQ(scaledScreenVector, ScreenVector(0, 1)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarWithRoundingAtHalfwayBoundary) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(5, 10); + auto scaledScreenVector = screenVector * 0.1f; + + // value less than 0.5 rounds down, greater than or equal to 0.5 rounds up + EXPECT_EQ(scaledScreenVector, ScreenVector(1, 1)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarDownwards) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(6, 12); + auto scaledScreenVector = screenVector * 0.5f; + + EXPECT_EQ(scaledScreenVector, ScreenVector(3, 6)); + } + + TEST(ViewportScreen, ScreenVectorTransformedByScalarInplace) + { + using AzFramework::ScreenVector; + + auto screenVector = ScreenVector(13, 37); + screenVector *= 10.0f; + + EXPECT_EQ(screenVector, ScreenVector(130, 370)); + } + //////////////////////////////////////////////////////////////////////////////////////////////////////// // Other tests TEST(ViewportScreen, CanGetCameraTransformFromCameraViewAndBack) diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp index c5268b427f..86b3011836 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp @@ -1726,7 +1726,7 @@ void AZ::FFont::DrawScreenAlignedText3d( { return; } - AZ::Vector3 positionNDC = AzFramework::WorldToScreenNDC( + AZ::Vector3 positionNDC = AzFramework::WorldToScreenNdc( params.m_position, currentView->GetWorldToViewMatrix(), currentView->GetViewToClipMatrix()