diff --git a/Code/Editor/EditorModularViewportCameraComposer.cpp b/Code/Editor/EditorModularViewportCameraComposer.cpp index e375e98fb6..600f2089e6 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.cpp +++ b/Code/Editor/EditorModularViewportCameraComposer.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include namespace SandboxEditor @@ -94,7 +95,7 @@ namespace SandboxEditor cameras.AddCamera(m_firstPersonPanCamera); cameras.AddCamera(m_firstPersonTranslateCamera); cameras.AddCamera(m_firstPersonScrollCamera); - cameras.AddCamera(m_orbitCamera); + cameras.AddCamera(m_pivotCamera); }); return controller; @@ -131,8 +132,8 @@ namespace SandboxEditor m_firstPersonRotateCamera->SetActivationBeganFn(hideCursor); m_firstPersonRotateCamera->SetActivationEndedFn(showCursor); - m_firstPersonPanCamera = - AZStd::make_shared(SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan); + m_firstPersonPanCamera = AZStd::make_shared( + SandboxEditor::CameraFreePanChannelId(), AzFramework::LookPan, AzFramework::TranslatePivot); m_firstPersonPanCamera->m_panSpeedFn = [] { @@ -151,8 +152,8 @@ namespace SandboxEditor const auto translateCameraInputChannelIds = BuildTranslateCameraInputChannelIds(); - m_firstPersonTranslateCamera = - AZStd::make_shared(AzFramework::LookTranslation, translateCameraInputChannelIds); + m_firstPersonTranslateCamera = AZStd::make_shared( + translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot); m_firstPersonTranslateCamera->m_translateSpeedFn = [] { @@ -171,10 +172,10 @@ namespace SandboxEditor return SandboxEditor::CameraScrollSpeed(); }; - m_orbitCamera = AZStd::make_shared(SandboxEditor::CameraOrbitChannelId()); + m_pivotCamera = AZStd::make_shared(SandboxEditor::CameraPivotChannelId()); - m_orbitCamera->SetLookAtFn( - [viewportId = m_viewportId](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional + m_pivotCamera->SetPivotFn( + [viewportId = m_viewportId]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) { AZStd::optional lookAtAfterInterpolation; AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( @@ -182,109 +183,98 @@ namespace SandboxEditor &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::LookAtAfterInterpolation); // initially attempt to use the last set look at point after an interpolation has finished - if (lookAtAfterInterpolation.has_value()) + // note: ignore this if it is the same location as the camera (e.g. after go to position) + if (lookAtAfterInterpolation.has_value() && !lookAtAfterInterpolation->IsClose(position)) { return *lookAtAfterInterpolation; } - const float RayDistance = 1000.0f; - AzFramework::RenderGeometry::RayRequest ray; - ray.m_startWorldPosition = position; - ray.m_endWorldPosition = position + direction * RayDistance; - ray.m_onlyVisible = true; + // otherwise fall back to the selected entity pivot + AZStd::optional entityPivot; + AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( + entityPivot, AzToolsFramework::GetEntityContextId(), + &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); - AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult; - AzFramework::RenderGeometry::IntersectorBus::EventResult( - renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(), - &AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, ray); - - // attempt a ray intersection with any visible mesh and return the intersection position if successful - if (renderGeometryIntersectionResult) - { - return renderGeometryIntersectionResult.m_worldPosition; - } - - // if there is no selection or no intersection, fallback to default camera orbit behavior (ground plane - // intersection) - return {}; + // finally just use the identity + return entityPivot.value_or(AZ::Transform::CreateIdentity()).GetTranslation(); }); - m_orbitRotateCamera = AZStd::make_shared(SandboxEditor::CameraOrbitLookChannelId()); + m_pivotRotateCamera = AZStd::make_shared(SandboxEditor::CameraPivotLookChannelId()); - m_orbitRotateCamera->m_rotateSpeedFn = [] + m_pivotRotateCamera->m_rotateSpeedFn = [] { return SandboxEditor::CameraRotateSpeed(); }; - m_orbitRotateCamera->m_invertYawFn = [] + m_pivotRotateCamera->m_invertYawFn = [] { - return SandboxEditor::CameraOrbitYawRotationInverted(); + return SandboxEditor::CameraPivotYawRotationInverted(); }; - m_orbitTranslateCamera = - AZStd::make_shared(AzFramework::OrbitTranslation, translateCameraInputChannelIds); + m_pivotTranslateCamera = AZStd::make_shared( + translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslateOffset); - m_orbitTranslateCamera->m_translateSpeedFn = [] + m_pivotTranslateCamera->m_translateSpeedFn = [] { return SandboxEditor::CameraTranslateSpeed(); }; - m_orbitTranslateCamera->m_boostMultiplierFn = [] + m_pivotTranslateCamera->m_boostMultiplierFn = [] { return SandboxEditor::CameraBoostMultiplier(); }; - m_orbitDollyScrollCamera = AZStd::make_shared(); + m_pivotDollyScrollCamera = AZStd::make_shared(); - m_orbitDollyScrollCamera->m_scrollSpeedFn = [] + m_pivotDollyScrollCamera->m_scrollSpeedFn = [] { return SandboxEditor::CameraScrollSpeed(); }; - m_orbitDollyMoveCamera = - AZStd::make_shared(SandboxEditor::CameraOrbitDollyChannelId()); + m_pivotDollyMoveCamera = AZStd::make_shared(SandboxEditor::CameraPivotDollyChannelId()); - m_orbitDollyMoveCamera->m_cursorSpeedFn = [] + m_pivotDollyMoveCamera->m_motionSpeedFn = [] { return SandboxEditor::CameraDollyMotionSpeed(); }; - m_orbitPanCamera = AZStd::make_shared(SandboxEditor::CameraOrbitPanChannelId(), AzFramework::OrbitPan); + m_pivotPanCamera = AZStd::make_shared( + SandboxEditor::CameraPivotPanChannelId(), AzFramework::LookPan, AzFramework::TranslateOffset); - m_orbitPanCamera->m_panSpeedFn = [] + m_pivotPanCamera->m_panSpeedFn = [] { return SandboxEditor::CameraPanSpeed(); }; - m_orbitPanCamera->m_invertPanXFn = [] + m_pivotPanCamera->m_invertPanXFn = [] { return SandboxEditor::CameraPanInvertedX(); }; - m_orbitPanCamera->m_invertPanYFn = [] + m_pivotPanCamera->m_invertPanYFn = [] { return SandboxEditor::CameraPanInvertedY(); }; - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitRotateCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitTranslateCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyScrollCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitDollyMoveCamera); - m_orbitCamera->m_orbitCameras.AddCamera(m_orbitPanCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotRotateCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotTranslateCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyScrollCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotDollyMoveCamera); + m_pivotCamera->m_pivotCameras.AddCamera(m_pivotPanCamera); } void EditorModularViewportCameraComposer::OnEditorModularViewportCameraComposerSettingsChanged() { const auto translateCameraInputChannelIds = BuildTranslateCameraInputChannelIds(); m_firstPersonTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); - m_orbitTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); - m_firstPersonPanCamera->SetPanInputChannelId(SandboxEditor::CameraFreePanChannelId()); - m_orbitPanCamera->SetPanInputChannelId(SandboxEditor::CameraOrbitPanChannelId()); m_firstPersonRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraFreeLookChannelId()); - m_orbitRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraOrbitLookChannelId()); - m_orbitCamera->SetOrbitInputChannelId(SandboxEditor::CameraOrbitChannelId()); - m_orbitDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraOrbitDollyChannelId()); + + m_pivotCamera->SetPivotInputChannelId(SandboxEditor::CameraPivotChannelId()); + m_pivotTranslateCamera->SetTranslateCameraInputChannelIds(translateCameraInputChannelIds); + m_pivotPanCamera->SetPanInputChannelId(SandboxEditor::CameraPivotPanChannelId()); + m_pivotRotateCamera->SetRotateInputChannelId(SandboxEditor::CameraPivotLookChannelId()); + m_pivotDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraPivotDollyChannelId()); } void EditorModularViewportCameraComposer::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) @@ -295,8 +285,7 @@ namespace SandboxEditor AZ::TransformBus::EventResult(worldFromLocal, viewEntityId, &AZ::TransformBus::Events::GetWorldTM); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( - m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, - worldFromLocal); + m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, worldFromLocal); } else { diff --git a/Code/Editor/EditorModularViewportCameraComposer.h b/Code/Editor/EditorModularViewportCameraComposer.h index e6e71c976c..e691ca1c89 100644 --- a/Code/Editor/EditorModularViewportCameraComposer.h +++ b/Code/Editor/EditorModularViewportCameraComposer.h @@ -42,12 +42,12 @@ namespace SandboxEditor AZStd::shared_ptr m_firstPersonPanCamera; AZStd::shared_ptr m_firstPersonTranslateCamera; AZStd::shared_ptr m_firstPersonScrollCamera; - AZStd::shared_ptr m_orbitCamera; - AZStd::shared_ptr m_orbitRotateCamera; - AZStd::shared_ptr m_orbitTranslateCamera; - AZStd::shared_ptr m_orbitDollyScrollCamera; - AZStd::shared_ptr m_orbitDollyMoveCamera; - AZStd::shared_ptr m_orbitPanCamera; + AZStd::shared_ptr m_pivotCamera; + AZStd::shared_ptr m_pivotRotateCamera; + AZStd::shared_ptr m_pivotTranslateCamera; + AZStd::shared_ptr m_pivotDollyScrollCamera; + AZStd::shared_ptr m_pivotDollyMoveCamera; + AZStd::shared_ptr m_pivotPanCamera; AzFramework::ViewportId m_viewportId; }; diff --git a/Code/Editor/EditorPreferencesPageViewportCamera.cpp b/Code/Editor/EditorPreferencesPageViewportCamera.cpp index 368d2769f8..55b631e1f6 100644 --- a/Code/Editor/EditorPreferencesPageViewportCamera.cpp +++ b/Code/Editor/EditorPreferencesPageViewportCamera.cpp @@ -61,7 +61,7 @@ static AZStd::vector GetEditorInputNames() void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serialize) { serialize.Class() - ->Version(2) + ->Version(3) ->Field("TranslateSpeed", &CameraMovementSettings::m_translateSpeed) ->Field("RotateSpeed", &CameraMovementSettings::m_rotateSpeed) ->Field("BoostMultiplier", &CameraMovementSettings::m_boostMultiplier) @@ -73,12 +73,12 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Field("TranslateSmoothing", &CameraMovementSettings::m_translateSmoothing) ->Field("TranslateSmoothness", &CameraMovementSettings::m_translateSmoothness) ->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook) - ->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted) + ->Field("PivotYawRotationInverted", &CameraMovementSettings::m_pivotYawRotationInverted) ->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX) ->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY); serialize.Class() - ->Version(1) + ->Version(2) ->Field("TranslateForward", &CameraInputSettings::m_translateForwardChannelId) ->Field("TranslateBackward", &CameraInputSettings::m_translateBackwardChannelId) ->Field("TranslateLeft", &CameraInputSettings::m_translateLeftChannelId) @@ -86,12 +86,12 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Field("TranslateUp", &CameraInputSettings::m_translateUpChannelId) ->Field("TranslateDown", &CameraInputSettings::m_translateDownChannelId) ->Field("Boost", &CameraInputSettings::m_boostChannelId) - ->Field("Orbit", &CameraInputSettings::m_orbitChannelId) + ->Field("Pivot", &CameraInputSettings::m_pivotChannelId) ->Field("FreeLook", &CameraInputSettings::m_freeLookChannelId) ->Field("FreePan", &CameraInputSettings::m_freePanChannelId) - ->Field("OrbitLook", &CameraInputSettings::m_orbitLookChannelId) - ->Field("OrbitDolly", &CameraInputSettings::m_orbitDollyChannelId) - ->Field("OrbitPan", &CameraInputSettings::m_orbitPanChannelId); + ->Field("PivotLook", &CameraInputSettings::m_pivotLookChannelId) + ->Field("PivotDolly", &CameraInputSettings::m_pivotDollyChannelId) + ->Field("PivotPan", &CameraInputSettings::m_pivotPanChannelId); serialize.Class() ->Version(1) @@ -143,8 +143,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial ->Attribute(AZ::Edit::Attributes::Min, minValue) ->Attribute(AZ::Edit::Attributes::Visibility, &CameraMovementSettings::TranslateSmoothingVisibility) ->DataElement( - AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_orbitYawRotationInverted, "Camera Orbit Yaw Inverted", - "Inverted yaw rotation while orbiting") + AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_pivotYawRotationInverted, "Camera Pivot Yaw Inverted", + "Inverted yaw rotation while pivoting") ->DataElement( AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_panInvertedX, "Invert Pan X", "Invert direction of pan in local X axis") @@ -185,8 +185,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial "Key/button to move the camera more quickly") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitChannelId, "Orbit", - "Key/button to begin the camera orbit behavior") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotChannelId, "Pivot", + "Key/button to begin the camera pivot behavior") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freeLookChannelId, "Free Look", @@ -196,16 +196,16 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_freePanChannelId, "Free Pan", "Key/button to begin camera free pan") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitLookChannelId, "Orbit Look", - "Key/button to begin camera orbit look") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotLookChannelId, "Pivot Look", + "Key/button to begin camera pivot look") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitDollyChannelId, "Orbit Dolly", - "Key/button to begin camera orbit dolly") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotDollyChannelId, "Pivot Dolly", + "Key/button to begin camera pivot dolly") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames) ->DataElement( - AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_orbitPanChannelId, "Orbit Pan", - "Key/button to begin camera orbit pan") + AZ::Edit::UIHandlers::ComboBox, &CameraInputSettings::m_pivotPanChannelId, "Pivot Pan", + "Key/button to begin camera pivot pan") ->Attribute(AZ::Edit::Attributes::StringList, &GetEditorInputNames); editContext->Class("Viewport Preferences", "Viewport Preferences") @@ -264,7 +264,7 @@ void CEditorPreferencesPage_ViewportCamera::OnApply() SandboxEditor::SetCameraTranslateSmoothness(m_cameraMovementSettings.m_translateSmoothness); SandboxEditor::SetCameraTranslateSmoothingEnabled(m_cameraMovementSettings.m_translateSmoothing); SandboxEditor::SetCameraCaptureCursorForLook(m_cameraMovementSettings.m_captureCursorLook); - SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted); + SandboxEditor::SetCameraPivotYawRotationInverted(m_cameraMovementSettings.m_pivotYawRotationInverted); SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX); SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY); @@ -275,12 +275,12 @@ void CEditorPreferencesPage_ViewportCamera::OnApply() SandboxEditor::SetCameraTranslateUpChannelId(m_cameraInputSettings.m_translateUpChannelId); SandboxEditor::SetCameraTranslateDownChannelId(m_cameraInputSettings.m_translateDownChannelId); SandboxEditor::SetCameraTranslateBoostChannelId(m_cameraInputSettings.m_boostChannelId); - SandboxEditor::SetCameraOrbitChannelId(m_cameraInputSettings.m_orbitChannelId); + SandboxEditor::SetCameraPivotChannelId(m_cameraInputSettings.m_pivotChannelId); SandboxEditor::SetCameraFreeLookChannelId(m_cameraInputSettings.m_freeLookChannelId); SandboxEditor::SetCameraFreePanChannelId(m_cameraInputSettings.m_freePanChannelId); - SandboxEditor::SetCameraOrbitLookChannelId(m_cameraInputSettings.m_orbitLookChannelId); - SandboxEditor::SetCameraOrbitDollyChannelId(m_cameraInputSettings.m_orbitDollyChannelId); - SandboxEditor::SetCameraOrbitPanChannelId(m_cameraInputSettings.m_orbitPanChannelId); + SandboxEditor::SetCameraPivotLookChannelId(m_cameraInputSettings.m_pivotLookChannelId); + SandboxEditor::SetCameraPivotDollyChannelId(m_cameraInputSettings.m_pivotDollyChannelId); + SandboxEditor::SetCameraPivotPanChannelId(m_cameraInputSettings.m_pivotPanChannelId); SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Broadcast( &SandboxEditor::EditorModularViewportCameraComposerNotificationBus::Events::OnEditorModularViewportCameraComposerSettingsChanged); @@ -299,7 +299,7 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings() m_cameraMovementSettings.m_translateSmoothness = SandboxEditor::CameraTranslateSmoothness(); m_cameraMovementSettings.m_translateSmoothing = SandboxEditor::CameraTranslateSmoothingEnabled(); m_cameraMovementSettings.m_captureCursorLook = SandboxEditor::CameraCaptureCursorForLook(); - m_cameraMovementSettings.m_orbitYawRotationInverted = SandboxEditor::CameraOrbitYawRotationInverted(); + m_cameraMovementSettings.m_pivotYawRotationInverted = SandboxEditor::CameraPivotYawRotationInverted(); m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX(); m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY(); @@ -310,10 +310,10 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings() m_cameraInputSettings.m_translateUpChannelId = SandboxEditor::CameraTranslateUpChannelId().GetName(); m_cameraInputSettings.m_translateDownChannelId = SandboxEditor::CameraTranslateDownChannelId().GetName(); m_cameraInputSettings.m_boostChannelId = SandboxEditor::CameraTranslateBoostChannelId().GetName(); - m_cameraInputSettings.m_orbitChannelId = SandboxEditor::CameraOrbitChannelId().GetName(); + m_cameraInputSettings.m_pivotChannelId = SandboxEditor::CameraPivotChannelId().GetName(); m_cameraInputSettings.m_freeLookChannelId = SandboxEditor::CameraFreeLookChannelId().GetName(); m_cameraInputSettings.m_freePanChannelId = SandboxEditor::CameraFreePanChannelId().GetName(); - m_cameraInputSettings.m_orbitLookChannelId = SandboxEditor::CameraOrbitLookChannelId().GetName(); - m_cameraInputSettings.m_orbitDollyChannelId = SandboxEditor::CameraOrbitDollyChannelId().GetName(); - m_cameraInputSettings.m_orbitPanChannelId = SandboxEditor::CameraOrbitPanChannelId().GetName(); + m_cameraInputSettings.m_pivotLookChannelId = SandboxEditor::CameraPivotLookChannelId().GetName(); + m_cameraInputSettings.m_pivotDollyChannelId = SandboxEditor::CameraPivotDollyChannelId().GetName(); + m_cameraInputSettings.m_pivotPanChannelId = SandboxEditor::CameraPivotPanChannelId().GetName(); } diff --git a/Code/Editor/EditorPreferencesPageViewportCamera.h b/Code/Editor/EditorPreferencesPageViewportCamera.h index 31a728599f..01dc4664f6 100644 --- a/Code/Editor/EditorPreferencesPageViewportCamera.h +++ b/Code/Editor/EditorPreferencesPageViewportCamera.h @@ -54,7 +54,7 @@ private: float m_translateSmoothness; bool m_translateSmoothing; bool m_captureCursorLook; - bool m_orbitYawRotationInverted; + bool m_pivotYawRotationInverted; bool m_panInvertedX; bool m_panInvertedY; @@ -80,12 +80,12 @@ private: AZStd::string m_translateUpChannelId; AZStd::string m_translateDownChannelId; AZStd::string m_boostChannelId; - AZStd::string m_orbitChannelId; + AZStd::string m_pivotChannelId; AZStd::string m_freeLookChannelId; AZStd::string m_freePanChannelId; - AZStd::string m_orbitLookChannelId; - AZStd::string m_orbitDollyChannelId; - AZStd::string m_orbitPanChannelId; + AZStd::string m_pivotLookChannelId; + AZStd::string m_pivotDollyChannelId; + AZStd::string m_pivotPanChannelId; }; CameraMovementSettings m_cameraMovementSettings; diff --git a/Code/Editor/EditorViewportSettings.cpp b/Code/Editor/EditorViewportSettings.cpp index 063ec125ba..fe8efecd17 100644 --- a/Code/Editor/EditorViewportSettings.cpp +++ b/Code/Editor/EditorViewportSettings.cpp @@ -28,7 +28,7 @@ namespace SandboxEditor constexpr AZStd::string_view CameraRotateSpeedSetting = "/Amazon/Preferences/Editor/Camera/RotateSpeed"; constexpr AZStd::string_view CameraScrollSpeedSetting = "/Amazon/Preferences/Editor/Camera/DollyScrollSpeed"; constexpr AZStd::string_view CameraDollyMotionSpeedSetting = "/Amazon/Preferences/Editor/Camera/DollyMotionSpeed"; - constexpr AZStd::string_view CameraOrbitYawRotationInvertedSetting = "/Amazon/Preferences/Editor/Camera/YawRotationInverted"; + constexpr AZStd::string_view CameraPivotYawRotationInvertedSetting = "/Amazon/Preferences/Editor/Camera/YawRotationInverted"; constexpr AZStd::string_view CameraPanInvertedXSetting = "/Amazon/Preferences/Editor/Camera/PanInvertedX"; constexpr AZStd::string_view CameraPanInvertedYSetting = "/Amazon/Preferences/Editor/Camera/PanInvertedY"; constexpr AZStd::string_view CameraPanSpeedSetting = "/Amazon/Preferences/Editor/Camera/PanSpeed"; @@ -44,12 +44,12 @@ namespace SandboxEditor constexpr AZStd::string_view CameraTranslateUpIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpId"; constexpr AZStd::string_view CameraTranslateDownIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateUpDownId"; constexpr AZStd::string_view CameraTranslateBoostIdSetting = "/Amazon/Preferences/Editor/Camera/TranslateBoostId"; - constexpr AZStd::string_view CameraOrbitIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitId"; + constexpr AZStd::string_view CameraPivotIdSetting = "/Amazon/Preferences/Editor/Camera/PivotId"; constexpr AZStd::string_view CameraFreeLookIdSetting = "/Amazon/Preferences/Editor/Camera/FreeLookId"; constexpr AZStd::string_view CameraFreePanIdSetting = "/Amazon/Preferences/Editor/Camera/FreePanId"; - constexpr AZStd::string_view CameraOrbitLookIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitLookId"; - constexpr AZStd::string_view CameraOrbitDollyIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitDollyId"; - constexpr AZStd::string_view CameraOrbitPanIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitPanId"; + constexpr AZStd::string_view CameraPivotLookIdSetting = "/Amazon/Preferences/Editor/Camera/PivotLookId"; + constexpr AZStd::string_view CameraPivotDollyIdSetting = "/Amazon/Preferences/Editor/Camera/PivotDollyId"; + constexpr AZStd::string_view CameraPivotPanIdSetting = "/Amazon/Preferences/Editor/Camera/PivotPanId"; template void SetRegistry(const AZStd::string_view setting, T&& value) @@ -239,14 +239,14 @@ namespace SandboxEditor SetRegistry(CameraDollyMotionSpeedSetting, speed); } - bool CameraOrbitYawRotationInverted() + bool CameraPivotYawRotationInverted() { - return GetRegistry(CameraOrbitYawRotationInvertedSetting, false); + return GetRegistry(CameraPivotYawRotationInvertedSetting, false); } - void SetCameraOrbitYawRotationInverted(const bool inverted) + void SetCameraPivotYawRotationInverted(const bool inverted) { - SetRegistry(CameraOrbitYawRotationInvertedSetting, inverted); + SetRegistry(CameraPivotYawRotationInvertedSetting, inverted); } bool CameraPanInvertedX() @@ -403,14 +403,14 @@ namespace SandboxEditor SetRegistry(CameraTranslateBoostIdSetting, cameraTranslateBoostId); } - AzFramework::InputChannelId CameraOrbitChannelId() + AzFramework::InputChannelId CameraPivotChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotIdSetting, AZStd::string("keyboard_key_modifier_alt_l")).c_str()); } - void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId) + void SetCameraPivotChannelId(AZStd::string_view cameraPivotId) { - SetRegistry(CameraOrbitIdSetting, cameraOrbitId); + SetRegistry(CameraPivotIdSetting, cameraPivotId); } AzFramework::InputChannelId CameraFreeLookChannelId() @@ -433,33 +433,33 @@ namespace SandboxEditor SetRegistry(CameraFreePanIdSetting, cameraFreePanId); } - AzFramework::InputChannelId CameraOrbitLookChannelId() + AzFramework::InputChannelId CameraPivotLookChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitLookIdSetting, AZStd::string("mouse_button_left")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotLookIdSetting, AZStd::string("mouse_button_left")).c_str()); } - void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId) + void SetCameraPivotLookChannelId(AZStd::string_view cameraPivotLookId) { - SetRegistry(CameraOrbitLookIdSetting, cameraOrbitLookId); + SetRegistry(CameraPivotLookIdSetting, cameraPivotLookId); } - AzFramework::InputChannelId CameraOrbitDollyChannelId() + AzFramework::InputChannelId CameraPivotDollyChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotDollyIdSetting, AZStd::string("mouse_button_right")).c_str()); } - void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId) + void SetCameraPivotDollyChannelId(AZStd::string_view cameraPivotDollyId) { - SetRegistry(CameraOrbitDollyIdSetting, cameraOrbitDollyId); + SetRegistry(CameraPivotDollyIdSetting, cameraPivotDollyId); } - AzFramework::InputChannelId CameraOrbitPanChannelId() + AzFramework::InputChannelId CameraPivotPanChannelId() { - return AzFramework::InputChannelId(GetRegistry(CameraOrbitPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); + return AzFramework::InputChannelId(GetRegistry(CameraPivotPanIdSetting, AZStd::string("mouse_button_middle")).c_str()); } - void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId) + void SetCameraPivotPanChannelId(AZStd::string_view cameraPivotPanId) { - SetRegistry(CameraOrbitPanIdSetting, cameraOrbitPanId); + SetRegistry(CameraPivotPanIdSetting, cameraPivotPanId); } } // namespace SandboxEditor diff --git a/Code/Editor/EditorViewportSettings.h b/Code/Editor/EditorViewportSettings.h index 20d397a29e..d1271017c6 100644 --- a/Code/Editor/EditorViewportSettings.h +++ b/Code/Editor/EditorViewportSettings.h @@ -71,8 +71,8 @@ namespace SandboxEditor SANDBOX_API float CameraDollyMotionSpeed(); SANDBOX_API void SetCameraDollyMotionSpeed(float speed); - SANDBOX_API bool CameraOrbitYawRotationInverted(); - SANDBOX_API void SetCameraOrbitYawRotationInverted(bool inverted); + SANDBOX_API bool CameraPivotYawRotationInverted(); + SANDBOX_API void SetCameraPivotYawRotationInverted(bool inverted); SANDBOX_API bool CameraPanInvertedX(); SANDBOX_API void SetCameraPanInvertedX(bool inverted); @@ -119,8 +119,8 @@ namespace SandboxEditor SANDBOX_API AzFramework::InputChannelId CameraTranslateBoostChannelId(); SANDBOX_API void SetCameraTranslateBoostChannelId(AZStd::string_view cameraTranslateBoostId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitChannelId(); - SANDBOX_API void SetCameraOrbitChannelId(AZStd::string_view cameraOrbitId); + SANDBOX_API AzFramework::InputChannelId CameraPivotChannelId(); + SANDBOX_API void SetCameraPivotChannelId(AZStd::string_view cameraPivotId); SANDBOX_API AzFramework::InputChannelId CameraFreeLookChannelId(); SANDBOX_API void SetCameraFreeLookChannelId(AZStd::string_view cameraFreeLookId); @@ -128,12 +128,12 @@ namespace SandboxEditor SANDBOX_API AzFramework::InputChannelId CameraFreePanChannelId(); SANDBOX_API void SetCameraFreePanChannelId(AZStd::string_view cameraFreePanId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitLookChannelId(); - SANDBOX_API void SetCameraOrbitLookChannelId(AZStd::string_view cameraOrbitLookId); + SANDBOX_API AzFramework::InputChannelId CameraPivotLookChannelId(); + SANDBOX_API void SetCameraPivotLookChannelId(AZStd::string_view cameraPivotLookId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitDollyChannelId(); - SANDBOX_API void SetCameraOrbitDollyChannelId(AZStd::string_view cameraOrbitDollyId); + SANDBOX_API AzFramework::InputChannelId CameraPivotDollyChannelId(); + SANDBOX_API void SetCameraPivotDollyChannelId(AZStd::string_view cameraPivotDollyId); - SANDBOX_API AzFramework::InputChannelId CameraOrbitPanChannelId(); - SANDBOX_API void SetCameraOrbitPanChannelId(AZStd::string_view cameraOrbitPanId); + SANDBOX_API AzFramework::InputChannelId CameraPivotPanChannelId(); + SANDBOX_API void SetCameraPivotPanChannelId(AZStd::string_view cameraPivotPanId); } // namespace SandboxEditor diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 18667ac153..a3eb4d8329 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -17,15 +17,6 @@ namespace AzFramework { - AZ_CVAR( - float, - ed_cameraSystemDefaultPlaneHeight, - 34.0f, - nullptr, - AZ::ConsoleFunctorFlags::Null, - "The default height of the ground plane to do intersection tests against when orbiting"); - AZ_CVAR(float, ed_cameraSystemMinOrbitDistance, 10.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ_CVAR(float, ed_cameraSystemMaxOrbitDistance, 50.0f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); AZ_CVAR( bool, ed_cameraSystemUseCursor, @@ -135,8 +126,8 @@ namespace AzFramework camera.m_pitch = eulerAngles.GetX(); camera.m_yaw = eulerAngles.GetZ(); - // note: m_lookDist is negative so we must invert it here - camera.m_lookAt = transform.GetTranslation() + (camera.Rotation().GetBasisY() * -camera.m_lookDist); + camera.m_pivot = transform.GetTranslation(); + camera.m_offset = AZ::Vector3::CreateZero(); } bool CameraSystem::HandleEvents(const InputEvent& event) @@ -320,14 +311,8 @@ namespace AzFramework nextCamera.m_pitch -= float(cursorDelta.m_y) * rotateSpeed * Invert(m_invertPitchFn()); nextCamera.m_yaw -= float(cursorDelta.m_x) * rotateSpeed * Invert(m_invertYawFn()); - const auto clampRotation = [](const float angle) - { - return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); - }; - - nextCamera.m_yaw = clampRotation(nextCamera.m_yaw); - // clamp pitch to be +/-90 degrees - nextCamera.m_pitch = AZ::GetClamp(nextCamera.m_pitch, -AZ::Constants::HalfPi, AZ::Constants::HalfPi); + nextCamera.m_yaw = WrapYawRotation(nextCamera.m_yaw); + nextCamera.m_pitch = ClampPitchRotation(nextCamera.m_pitch); return nextCamera; } @@ -337,9 +322,10 @@ namespace AzFramework m_rotateChannelId = rotateChannelId; } - PanCameraInput::PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn) + PanCameraInput::PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn, TranslationDeltaFn translationDeltaFn) : m_panAxesFn(AZStd::move(panAxesFn)) , m_panChannelId(panChannelId) + , m_translationDeltaFn(translationDeltaFn) { m_panSpeedFn = []() constexpr { @@ -375,11 +361,11 @@ namespace AzFramework const auto panAxes = m_panAxesFn(nextCamera); const float panSpeed = m_panSpeedFn(); - const auto deltaPanX = float(cursorDelta.m_x) * panAxes.m_horizontalAxis * panSpeed; - const auto deltaPanY = float(cursorDelta.m_y) * panAxes.m_verticalAxis * panSpeed; + const auto deltaPanX = aznumeric_cast(cursorDelta.m_x) * panAxes.m_horizontalAxis * panSpeed; + const auto deltaPanY = aznumeric_cast(cursorDelta.m_y) * panAxes.m_verticalAxis * panSpeed; - nextCamera.m_lookAt += deltaPanX * Invert(m_invertPanXFn()); - nextCamera.m_lookAt += deltaPanY * -Invert(m_invertPanYFn()); + m_translationDeltaFn(nextCamera, deltaPanX * Invert(m_invertPanXFn())); + m_translationDeltaFn(nextCamera, deltaPanY * -Invert(m_invertPanYFn())); return nextCamera; } @@ -426,8 +412,11 @@ namespace AzFramework } TranslateCameraInput::TranslateCameraInput( - TranslationAxesFn translationAxesFn, const TranslateCameraInputChannelIds& translateCameraInputChannelIds) + const TranslateCameraInputChannelIds& translateCameraInputChannelIds, + TranslationAxesFn translationAxesFn, + TranslationDeltaFn translateDeltaFn) : m_translationAxesFn(AZStd::move(translationAxesFn)) + , m_translateDeltaFn(AZStd::move(translateDeltaFn)) , m_translateCameraInputChannelIds(translateCameraInputChannelIds) { m_translateSpeedFn = []() constexpr @@ -497,32 +486,32 @@ namespace AzFramework if ((m_translation & TranslationType::Forward) == TranslationType::Forward) { - nextCamera.m_lookAt += axisY * speed * deltaTime; + m_translateDeltaFn(nextCamera, axisY * speed * deltaTime); } if ((m_translation & TranslationType::Backward) == TranslationType::Backward) { - nextCamera.m_lookAt -= axisY * speed * deltaTime; + m_translateDeltaFn(nextCamera, -axisY * speed * deltaTime); } if ((m_translation & TranslationType::Left) == TranslationType::Left) { - nextCamera.m_lookAt -= axisX * speed * deltaTime; + m_translateDeltaFn(nextCamera, -axisX * speed * deltaTime); } if ((m_translation & TranslationType::Right) == TranslationType::Right) { - nextCamera.m_lookAt += axisX * speed * deltaTime; + m_translateDeltaFn(nextCamera, axisX * speed * deltaTime); } if ((m_translation & TranslationType::Up) == TranslationType::Up) { - nextCamera.m_lookAt += axisZ * speed * deltaTime; + m_translateDeltaFn(nextCamera, axisZ * speed * deltaTime); } if ((m_translation & TranslationType::Down) == TranslationType::Down) { - nextCamera.m_lookAt -= axisZ * speed * deltaTime; + m_translateDeltaFn(nextCamera, -axisZ * speed * deltaTime); } if (Ending()) @@ -544,16 +533,20 @@ namespace AzFramework m_translateCameraInputChannelIds = translateCameraInputChannelIds; } - OrbitCameraInput::OrbitCameraInput(const InputChannelId& orbitChannelId) - : m_orbitChannelId(orbitChannelId) + PivotCameraInput::PivotCameraInput(const InputChannelId& pivotChannelId) + : m_pivotChannelId(pivotChannelId) { + m_pivotFn = []([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) + { + return AZ::Vector3::CreateZero(); + }; } - bool OrbitCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta) + bool PivotCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, const float scrollDelta) { if (const auto* input = AZStd::get_if(&event)) { - if (input->m_channelId == m_orbitChannelId) + if (input->m_channelId == m_pivotChannelId) { if (input->m_state == InputChannel::State::Began) { @@ -568,85 +561,46 @@ namespace AzFramework if (Active()) { - return m_orbitCameras.HandleEvents(event, cursorDelta, scrollDelta); + return m_pivotCameras.HandleEvents(event, cursorDelta, scrollDelta); } return !Idle(); } - Camera OrbitCameraInput::StepCamera( + Camera PivotCameraInput::StepCamera( const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime) { Camera nextCamera = targetCamera; if (Beginning()) { - const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] - { - if (lookAtFn) - { - // pass through the camera's position and look vector for use in the lookAt function - if (const auto lookAt = lookAtFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY())) - { - // default to internal look at behavior if the look at point matches the camera translation - if (targetCamera.m_lookAt.IsClose(*lookAt)) - { - return false; - } - - auto transform = AZ::Transform::CreateLookAt(targetCamera.m_lookAt, *lookAt); - nextCamera.m_lookDist = -lookAt->GetDistance(targetCamera.m_lookAt); - UpdateCameraFromTransform(nextCamera, transform); - - return true; - } - } - return false; - }(); - - if (!hasLookAt) - { - float hit_distance = 0.0f; - AZ::Plane::CreateFromNormalAndPoint(AZ::Vector3::CreateAxisZ(), AZ::Vector3::CreateAxisZ(ed_cameraSystemDefaultPlaneHeight)) - .CastRay(targetCamera.Translation(), targetCamera.Rotation().GetBasisY(), hit_distance); - - if (hit_distance > 0.0f) - { - hit_distance = AZStd::min(hit_distance, ed_cameraSystemMaxOrbitDistance); - nextCamera.m_lookDist = -hit_distance; - nextCamera.m_lookAt = targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * hit_distance; - } - else - { - nextCamera.m_lookDist = -ed_cameraSystemMinOrbitDistance; - nextCamera.m_lookAt = - targetCamera.Translation() + targetCamera.Rotation().GetBasisY() * ed_cameraSystemMinOrbitDistance; - } - } + nextCamera.m_pivot = m_pivotFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY()); + nextCamera.m_offset = nextCamera.View().TransformPoint(targetCamera.Translation()); } if (Active()) { - nextCamera = m_orbitCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime); + MovePivotDetached(nextCamera, m_pivotFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY())); + nextCamera = m_pivotCameras.StepCamera(nextCamera, cursorDelta, scrollDelta, deltaTime); } if (Ending()) { - m_orbitCameras.Reset(); + m_pivotCameras.Reset(); - nextCamera.m_lookAt = nextCamera.Translation(); - nextCamera.m_lookDist = 0.0f; + nextCamera.m_pivot = nextCamera.Translation(); + nextCamera.m_offset = AZ::Vector3::CreateZero(); } return nextCamera; } - void OrbitCameraInput::SetOrbitInputChannelId(const InputChannelId& orbitChanneId) + void PivotCameraInput::SetPivotInputChannelId(const InputChannelId& pivotChanneId) { - m_orbitChannelId = orbitChanneId; + m_pivotChannelId = pivotChanneId; } - OrbitDollyScrollCameraInput::OrbitDollyScrollCameraInput() + PivotDollyScrollCameraInput::PivotDollyScrollCameraInput() { m_scrollSpeedFn = []() constexpr { @@ -654,7 +608,7 @@ namespace AzFramework }; } - bool OrbitDollyScrollCameraInput::HandleEvents( + bool PivotDollyScrollCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) { if (const auto* scroll = AZStd::get_if(&event)) @@ -665,46 +619,61 @@ namespace AzFramework return !Idle(); } - Camera OrbitDollyScrollCameraInput::StepCamera( + static Camera PivotDolly(const Camera& targetCamera, const float delta) + { + Camera nextCamera = targetCamera; + + const auto pivotDirection = targetCamera.m_offset.GetNormalized(); + nextCamera.m_offset -= pivotDirection * delta; + const auto pivotDot = targetCamera.m_offset.Dot(nextCamera.m_offset); + const auto distance = nextCamera.m_offset.GetLength() * AZ::GetSign(pivotDot); + + const auto minDistance = 0.01f; + if (distance < minDistance || pivotDot < 0.0f) + { + nextCamera.m_offset = pivotDirection * minDistance; + } + + return nextCamera; + } + + Camera PivotDollyScrollCameraInput::StepCamera( const Camera& targetCamera, [[maybe_unused]] const ScreenVector& cursorDelta, const float scrollDelta, [[maybe_unused]] const float deltaTime) { - Camera nextCamera = targetCamera; - nextCamera.m_lookDist = AZ::GetMin(nextCamera.m_lookDist + scrollDelta * m_scrollSpeedFn(), 0.0f); + const auto nextCamera = PivotDolly(targetCamera, aznumeric_cast(scrollDelta) * m_scrollSpeedFn()); EndActivation(); return nextCamera; } - OrbitDollyCursorMoveCameraInput::OrbitDollyCursorMoveCameraInput(const InputChannelId& dollyChannelId) + PivotDollyMotionCameraInput::PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId) : m_dollyChannelId(dollyChannelId) { - m_cursorSpeedFn = []() constexpr + m_motionSpeedFn = []() constexpr { return 0.01f; }; } - bool OrbitDollyCursorMoveCameraInput::HandleEvents( + bool PivotDollyMotionCameraInput::HandleEvents( const InputEvent& event, [[maybe_unused]] const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta) { HandleActivationEvents(event, m_dollyChannelId, cursorDelta, m_clickDetector, *this); return CameraInputUpdatingAfterMotion(*this); } - Camera OrbitDollyCursorMoveCameraInput::StepCamera( + Camera PivotDollyMotionCameraInput::StepCamera( const Camera& targetCamera, const ScreenVector& cursorDelta, [[maybe_unused]] const float scrollDelta, [[maybe_unused]] const float deltaTime) { - Camera nextCamera = targetCamera; - nextCamera.m_lookDist = AZ::GetMin(nextCamera.m_lookDist + float(cursorDelta.m_y) * m_cursorSpeedFn(), 0.0f); - return nextCamera; + return PivotDolly(targetCamera, aznumeric_cast(cursorDelta.m_y) * m_motionSpeedFn()); } - void OrbitDollyCursorMoveCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId) + void PivotDollyMotionCameraInput::SetDollyInputChannelId(const InputChannelId& dollyChannelId) { m_dollyChannelId = dollyChannelId; } @@ -739,7 +708,7 @@ namespace AzFramework const auto translation_basis = LookTranslation(nextCamera); const auto axisY = translation_basis.GetBasisY(); - nextCamera.m_lookAt += axisY * scrollDelta * m_scrollSpeedFn(); + nextCamera.m_pivot += axisY * scrollDelta * m_scrollSpeedFn(); EndActivation(); @@ -790,13 +759,13 @@ namespace AzFramework { const float moveRate = AZStd::exp2(cameraProps.m_translateSmoothnessFn()); const float moveTime = AZStd::exp2(-moveRate * deltaTime); - camera.m_lookDist = AZ::Lerp(targetCamera.m_lookDist, currentCamera.m_lookDist, moveTime); - camera.m_lookAt = targetCamera.m_lookAt.Lerp(currentCamera.m_lookAt, moveTime); + camera.m_pivot = targetCamera.m_pivot.Lerp(currentCamera.m_pivot, moveTime); + camera.m_offset = targetCamera.m_offset.Lerp(currentCamera.m_offset, moveTime); } else { - camera.m_lookDist = targetCamera.m_lookDist; - camera.m_lookAt = targetCamera.m_lookAt; + camera.m_pivot = targetCamera.m_pivot; + camera.m_offset = targetCamera.m_offset; } return camera; diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index 69e8b66434..c44d951292 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -29,17 +29,15 @@ namespace AzFramework //! @note Order of rotation is Z, Y, X. AZ::Vector3 EulerAngles(const AZ::Matrix3x3& orientation); - //! A simple camera representation using spherical coordinates as input (pitch, yaw and look distance). + //! A simple camera representation using spherical coordinates as input (pitch, yaw, pivot and offset). //! The cameras transform and view can be obtained through accessor functions that use the internal //! spherical coordinates to calculate the position and orientation. struct Camera { - AZ::Vector3 m_lookAt = AZ::Vector3::CreateZero(); //!< Position of camera when m_lookDist is zero, - //!< or position of m_lookAt when m_lookDist is greater - //!< than zero. - float m_yaw{ 0.0 }; //!< Yaw rotation of camera (stored in radians) usually clamped to 0-360 degrees (0-2Pi radians). - float m_pitch{ 0.0 }; //!< Pitch rotation of the camera (stored in radians) usually clamped to +/-90 degrees (-Pi/2 - Pi/2 radians). - float m_lookDist{ 0.0 }; //!< Zero gives first person free look, otherwise orbit about m_lookAt + AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); //!< Pivot point to rotate about (modified in world space). + AZ::Vector3 m_offset = AZ::Vector3::CreateZero(); //!< Offset relative to pivot (modified in camera space). + float m_yaw = 0.0f; //!< Yaw rotation of camera (stored in radians) usually clamped to 0-360 degrees (0-2Pi radians). + float m_pitch = 0.0f; //!< Pitch rotation of the camera (stored in radians) usually clamped to +/-90 degrees (-Pi/2 - Pi/2 radians). //! View camera transform (V in model-view-projection matrix (MVP)). AZ::Transform View() const; @@ -51,6 +49,15 @@ namespace AzFramework AZ::Vector3 Translation() const; }; + //! Helper to allow the pivot to be positioned without altering the camera's position. + inline void MovePivotDetached(Camera& camera, const AZ::Vector3& pivot) + { + const auto& view = camera.View(); + const auto delta = view.TransformPoint(pivot) - view.TransformPoint(camera.m_pivot); + camera.m_offset -= delta; + camera.m_pivot = pivot; + } + inline AZ::Transform Camera::View() const { return Transform().GetInverse(); @@ -58,8 +65,8 @@ namespace AzFramework inline AZ::Transform Camera::Transform() const { - return AZ::Transform::CreateTranslation(m_lookAt) * AZ::Transform::CreateRotationZ(m_yaw) * - AZ::Transform::CreateRotationX(m_pitch) * AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(m_lookDist)); + return AZ::Transform::CreateTranslation(m_pivot) * AZ::Transform::CreateRotationZ(m_yaw) * AZ::Transform::CreateRotationX(m_pitch) * + AZ::Transform::CreateTranslation(m_offset); } inline AZ::Matrix3x3 Camera::Rotation() const @@ -279,21 +286,37 @@ namespace AzFramework public: bool HandleEvents(const InputEvent& event); Camera StepCamera(const Camera& targetCamera, float deltaTime); - bool HandlingEvents() const - { - return m_handlingEvents; - } + bool HandlingEvents() const; Cameras m_cameras; //!< Represents a collection of camera inputs that together provide a camera controller. private: - ScreenVector m_motionDelta; //!< The delta used for look/orbit/pan (rotation + translation) - two dimensional. + ScreenVector m_motionDelta; //!< The delta used for look/pivot/pan (rotation + translation) - two dimensional. CursorState m_cursorState; //!< The current and previous position of the cursor (used to calculate movement delta). float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. bool m_handlingEvents = false; //!< Is the camera system currently handling events (events are consumed and not propagated). }; - //! A camera input to handle motion deltas that can rotate or orbit the camera. + inline bool CameraSystem::HandlingEvents() const + { + return m_handlingEvents; + } + + //! Clamps pitch to be +/-90 degrees (-Pi/2, Pi/2). + //! @param pitch Pitch angle in radians. + inline float ClampPitchRotation(const float pitch) + { + return AZ::GetClamp(pitch, -AZ::Constants::HalfPi, AZ::Constants::HalfPi); + } + + //! Ensures yaw wraps between 0 and 360 degrees (0, 2Pi). + //! @param yaw Yaw angle in radians. + inline float WrapYawRotation(const float yaw) + { + return AZStd::fmod(yaw + AZ::Constants::TwoPi, AZ::Constants::TwoPi); + } + + //! A camera input to handle motion deltas that can rotate or pivot the camera. class RotateCameraInput : public CameraInput { public: @@ -332,8 +355,8 @@ namespace AzFramework return { orientation.GetBasisX(), orientation.GetBasisZ() }; } - //! PanAxes to use while in 'orbit' camera behavior. - inline PanAxes OrbitPan(const Camera& camera) + //! PanAxes to use while in 'pivot' camera behavior. + inline PanAxes PivotPan(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -347,11 +370,23 @@ namespace AzFramework return { basisX, basisY }; } + using TranslationDeltaFn = AZStd::function; + + inline void TranslatePivot(Camera& camera, const AZ::Vector3& delta) + { + camera.m_pivot += delta; + } + + inline void TranslateOffset(Camera& camera, const AZ::Vector3& delta) + { + camera.m_offset += camera.View().TransformVector(delta); + } + //! A camera input to handle motion deltas that can pan the camera (translate in two axes). class PanCameraInput : public CameraInput { public: - PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn); + PanCameraInput(const InputChannelId& panChannelId, PanAxesFn panAxesFn, TranslationDeltaFn translationDeltaFn); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -365,6 +400,7 @@ namespace AzFramework private: PanAxesFn m_panAxesFn; //!< Builder for the particular pan axes (provided in the constructor). + TranslationDeltaFn m_translationDeltaFn; //!< How to apply the translation delta to the camera offset or pivot. InputChannelId m_panChannelId; //!< Input channel to begin the pan camera input. ClickDetector m_clickDetector; //!< Used to determine when a sufficient motion delta has occurred after an initial discrete input //!< event has started (press and move event). @@ -385,8 +421,8 @@ namespace AzFramework return AZ::Matrix3x3::CreateFromColumns(basisX, basisY, basisZ); } - //! TranslationAxes to use while in 'orbit' camera behavior. - inline AZ::Matrix3x3 OrbitTranslation(const Camera& camera) + //! TranslationAxes to use while in 'pivot' camera behavior. + inline AZ::Matrix3x3 PivotTranslation(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -417,8 +453,10 @@ namespace AzFramework class TranslateCameraInput : public CameraInput { public: - explicit TranslateCameraInput( - TranslationAxesFn translationAxesFn, const TranslateCameraInputChannelIds& translateCameraInputChannelIds); + TranslateCameraInput( + const TranslateCameraInputChannelIds& translateCameraInputChannelIds, + TranslationAxesFn translationAxesFn, + TranslationDeltaFn translateDeltaFn); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -492,15 +530,16 @@ namespace AzFramework TranslationType m_translation = TranslationType::Nil; //!< Types of translation the camera input is under. TranslationAxesFn m_translationAxesFn; //!< Builder for translation axes. + TranslationDeltaFn m_translateDeltaFn; //!< How to apply the translation delta to the camera offset or pivot. TranslateCameraInputChannelIds m_translateCameraInputChannelIds; //!< Input channel ids that map to internal translation types. bool m_boost = false; //!< Is the translation speed currently being multiplied/scaled upwards. }; - //! A camera input to handle discrete scroll events that can modify the camera look distance. - class OrbitDollyScrollCameraInput : public CameraInput + //! A camera input to handle discrete scroll events that can modify the camera pivot distance. + class PivotDollyScrollCameraInput : public CameraInput { public: - OrbitDollyScrollCameraInput(); + PivotDollyScrollCameraInput(); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -509,11 +548,11 @@ namespace AzFramework AZStd::function m_scrollSpeedFn; }; - //! A camera input to handle motion deltas that can modify the camera look distance. - class OrbitDollyCursorMoveCameraInput : public CameraInput + //! A camera input to handle motion deltas that can modify the camera pivot distance. + class PivotDollyMotionCameraInput : public CameraInput { public: - explicit OrbitDollyCursorMoveCameraInput(const InputChannelId& dollyChannelId); + explicit PivotDollyMotionCameraInput(const InputChannelId& dollyChannelId); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; @@ -521,7 +560,7 @@ namespace AzFramework void SetDollyInputChannelId(const InputChannelId& dollyChannelId); - AZStd::function m_cursorSpeedFn; + AZStd::function m_motionSpeedFn; private: InputChannelId m_dollyChannelId; //!< Input channel to begin the dolly cursor camera input. @@ -544,36 +583,36 @@ namespace AzFramework //! A camera input that doubles as its own set of camera inputs. //! It is 'exclusive', so does not overlap with other sibling camera inputs - it runs its own set of camera inputs as 'children'. - class OrbitCameraInput : public CameraInput + class PivotCameraInput : public CameraInput { public: - using LookAtFn = AZStd::function(const AZ::Vector3& position, const AZ::Vector3& direction)>; + using PivotFn = AZStd::function; - explicit OrbitCameraInput(const InputChannelId& orbitChannelId); + explicit PivotCameraInput(const InputChannelId& pivotChannelId); // CameraInput overrides ... bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override; Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; bool Exclusive() const override; - void SetOrbitInputChannelId(const InputChannelId& orbitChanneId); + void SetPivotInputChannelId(const InputChannelId& pivotChanneId); - Cameras m_orbitCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive). + Cameras m_pivotCameras; //!< The camera inputs to run when this camera input is active (only these will run as it is exclusive). - //! Override the default behavior for how a look-at point is calculated. - void SetLookAtFn(const LookAtFn& lookAtFn); + //! Override the default behavior for how a pivot point is calculated. + void SetPivotFn(PivotFn pivotFn); private: - InputChannelId m_orbitChannelId; //!< Input channel to begin the orbit camera input. - LookAtFn m_lookAtFn; //!< The look-at behavior to use for this orbit camera (how is the look-at point calculated/retrieved). + InputChannelId m_pivotChannelId; //!< Input channel to begin the pivot camera input. + PivotFn m_pivotFn; //!< The pivot position to use for this pivot camera (how is the pivot point calculated/retrieved). }; - inline void OrbitCameraInput::SetLookAtFn(const LookAtFn& lookAtFn) + inline void PivotCameraInput::SetPivotFn(PivotFn pivotFn) { - m_lookAtFn = lookAtFn; + m_pivotFn = AZStd::move(pivotFn); } - inline bool OrbitCameraInput::Exclusive() const + inline bool PivotCameraInput::Exclusive() const { return true; } diff --git a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp index df7d22f7ca..1e0c805587 100644 --- a/Code/Framework/AzFramework/Tests/CameraInputTests.cpp +++ b/Code/Framework/AzFramework/Tests/CameraInputTests.cpp @@ -26,7 +26,8 @@ namespace UnitTest { constexpr float deltaTime = 0.01666f; // 60fps const bool consumed = m_cameraSystem->HandleEvents(event); - m_camera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime); + m_targetCamera = m_cameraSystem->StepCamera(m_targetCamera, deltaTime); + m_camera = m_targetCamera; // no smoothing return consumed; } @@ -45,20 +46,38 @@ namespace UnitTest m_translateCameraInputChannelIds.m_boostChannelId = AzFramework::InputChannelId("keyboard_key_modifier_shift_l"); m_firstPersonRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Right); - m_firstPersonTranslateCamera = - AZStd::make_shared(AzFramework::LookTranslation, m_translateCameraInputChannelIds); + // set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth. + m_firstPersonRotateCamera->m_rotateSpeedFn = []() + { + return 0.001f; + }; + + m_firstPersonTranslateCamera = AZStd::make_shared( + m_translateCameraInputChannelIds, AzFramework::LookTranslation, AzFramework::TranslatePivot); + + m_pivotCamera = AZStd::make_shared(m_pivotChannelId); + m_pivotCamera->SetPivotFn( + [this](const AZ::Vector3&, const AZ::Vector3&) + { + return m_pivot; + }); + + auto pivotRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left); + // set rotate speed to be a value that will scale motion delta (pixels moved) by a thousandth. + pivotRotateCamera->m_rotateSpeedFn = []() + { + return 0.001f; + }; - m_orbitCamera = AZStd::make_shared(m_orbitChannelId); - auto orbitRotateCamera = AZStd::make_shared(AzFramework::InputDeviceMouse::Button::Left); - auto orbitTranslateCamera = - AZStd::make_shared(AzFramework::OrbitTranslation, m_translateCameraInputChannelIds); + auto pivotTranslateCamera = AZStd::make_shared( + m_translateCameraInputChannelIds, AzFramework::PivotTranslation, AzFramework::TranslateOffset); - m_orbitCamera->m_orbitCameras.AddCamera(orbitRotateCamera); - m_orbitCamera->m_orbitCameras.AddCamera(orbitTranslateCamera); + m_pivotCamera->m_pivotCameras.AddCamera(pivotRotateCamera); + m_pivotCamera->m_pivotCameras.AddCamera(pivotTranslateCamera); m_cameraSystem->m_cameras.AddCamera(m_firstPersonRotateCamera); m_cameraSystem->m_cameras.AddCamera(m_firstPersonTranslateCamera); - m_cameraSystem->m_cameras.AddCamera(m_orbitCamera); + m_cameraSystem->m_cameras.AddCamera(m_pivotCamera); // these tests rely on using motion delta, not cursor positions (default is true) AzFramework::ed_cameraSystemUseCursor = false; @@ -68,7 +87,7 @@ namespace UnitTest { AzFramework::ed_cameraSystemUseCursor = true; - m_orbitCamera.reset(); + m_pivotCamera.reset(); m_firstPersonRotateCamera.reset(); m_firstPersonTranslateCamera.reset(); @@ -78,24 +97,29 @@ namespace UnitTest AllocatorsTestFixture::TearDown(); } - AzFramework::InputChannelId m_orbitChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l"); + AzFramework::InputChannelId m_pivotChannelId = AzFramework::InputChannelId("keyboard_key_modifier_alt_l"); AzFramework::TranslateCameraInputChannelIds m_translateCameraInputChannelIds; AZStd::shared_ptr m_firstPersonRotateCamera; AZStd::shared_ptr m_firstPersonTranslateCamera; - AZStd::shared_ptr m_orbitCamera; + AZStd::shared_ptr m_pivotCamera; + AZ::Vector3 m_pivot = AZ::Vector3::CreateZero(); + + //! This is approximately Pi/2 * 1000 - this can be used to rotate the camera 90 degrees (pitch or yaw based + //! on vertical or horizontal motion) as the rotate speed function is set to be 1/1000. + inline static const int PixelMotionDelta = 1570; }; - TEST_F(CameraInputFixture, BeginAndEndOrbitCameraInputConsumesCorrectEvents) + TEST_F(CameraInputFixture, BeginAndEndPivotCameraInputConsumesCorrectEvents) { - // begin orbit camera + // begin pivot camera const bool consumed1 = HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceKeyboard::Key::ModifierAltL, AzFramework::InputChannel::State::Began }); - // begin listening for orbit rotate (click detector) - event is not consumed + // begin listening for pivot rotate (click detector) - event is not consumed const bool consumed2 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); - // begin orbit rotate (mouse has moved sufficient distance to initiate) + // begin pivot rotate (mouse has moved sufficient distance to initiate) const bool consumed3 = HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ 5 }); - // end orbit (mouse up) - event is not consumed + // end pivot (mouse up) - event is not consumed const bool consumed4 = HandleEventAndUpdate( AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Ended }); @@ -236,29 +260,110 @@ namespace UnitTest EXPECT_TRUE(activationEnded); } - TEST_F(CameraInputFixture, OrbitCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenOrbiting) + TEST_F(CameraInputFixture, PivotCameraInputHandlesLookAtPointAndSelfAtSamePositionWhenPivoting) { // create pathological lookAtFn that just returns the same position as the camera - m_orbitCamera->SetLookAtFn( + m_pivotCamera->SetPivotFn( [](const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction) { return position; }); + const auto expectedCameraPosition = AZ::Vector3(10.0f, 10.0f, 10.0f); AzFramework::UpdateCameraFromTransform( m_targetCamera, AZ::Transform::CreateFromQuaternionAndTranslation( - AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), AZ::Vector3(10.0f, 10.0f, 10.0f))); + AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(0.0f, 0.0f, 90.0f)), expectedCameraPosition)); + + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + + // verify the camera yaw has not changed and pivot point matches the expected camera position + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(AZ::DegToRad(90.0f), 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); + EXPECT_THAT(m_camera.m_pivot, IsClose(expectedCameraPosition)); + } + + TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesYawByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ PixelMotionDelta }); + + const float expectedYaw = AzFramework::WrapYawRotation(-AZ::Constants::HalfPi); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); + } + + TEST_F(CameraInputFixture, FirstPersonRotateCameraInputRotatesPitchByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-10.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Right, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta }); + + const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi); + + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(cameraStartingPosition)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateZero())); + } + + TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesPitchOffsetByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3::CreateAxisY(-20.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + m_pivot = AZ::Vector3::CreateAxisY(-10.0f); + + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::VerticalMotionEvent{ PixelMotionDelta }); + + const auto expectedCameraEndingPosition = AZ::Vector3(0.0f, -10.0f, 10.0f); + const float expectedPitch = AzFramework::ClampPitchRotation(-AZ::Constants::HalfPi); - m_camera = m_targetCamera; + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3::CreateAxisY(-10.0f))); + EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f)); + } + + TEST_F(CameraInputFixture, PivotRotateCameraInputRotatesYawOffsetByNinetyDegreesWithRequiredPixelDelta) + { + const auto cameraStartingPosition = AZ::Vector3(15.0f, -20.0f, 0.0f); + m_targetCamera.m_pivot = cameraStartingPosition; + + m_pivot = AZ::Vector3(10.0f, -10.0f, 0.0f); + + HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_pivotChannelId, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate( + AzFramework::DiscreteInputEvent{ AzFramework::InputDeviceMouse::Button::Left, AzFramework::InputChannel::State::Began }); + HandleEventAndUpdate(AzFramework::HorizontalMotionEvent{ -PixelMotionDelta }); - HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began }); + const auto expectedCameraEndingPosition = AZ::Vector3(20.0f, -5.0f, 0.0f); + const float expectedYaw = AzFramework::WrapYawRotation(AZ::Constants::HalfPi); - // verify the camera yaw has not changed and the look at point - // does not match that of the camera translation - using ::testing::Eq; - using ::testing::Not; - EXPECT_THAT(m_camera.m_yaw, Eq(AZ::DegToRad(90.0f))); - EXPECT_THAT(m_camera.m_lookAt, Not(IsClose(m_camera.Translation()))); + using ::testing::FloatNear; + EXPECT_THAT(m_camera.m_yaw, FloatNear(expectedYaw, 0.001f)); + EXPECT_THAT(m_camera.m_pitch, FloatNear(0.0f, 0.001f)); + EXPECT_THAT(m_camera.m_pivot, IsClose(m_pivot)); + EXPECT_THAT(m_camera.m_offset, IsClose(AZ::Vector3(5.0f, -10.0f, 0.0f))); + EXPECT_THAT(m_camera.Translation(), IsCloseTolerance(expectedCameraEndingPosition, 0.01f)); } } // namespace UnitTest diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 4956fbc1dc..32897ab4d9 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -10,7 +10,6 @@ #include #include -#include #include #include @@ -103,7 +102,6 @@ namespace AtomToolsFramework class ModularViewportCameraControllerInstance final : public AzFramework::MultiViewportControllerInstanceInterface , public ModularViewportCameraControllerRequestBus::Handler - , private AzFramework::ViewportDebugDisplayEventBus::Handler { public: explicit ModularViewportCameraControllerInstance(AzFramework::ViewportId viewportId, ModularViewportCameraController* controller); @@ -121,9 +119,6 @@ namespace AtomToolsFramework void ClearReferenceFrame() override; private: - // AzFramework::ViewportDebugDisplayEventBus overrides ... - void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override; - //! Update the reference frame after a change has been made to the camera //! view without updating the internal camera via user input. void RefreshReferenceFrame(); diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index bcc9a08f77..0bdd6fb55c 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -21,15 +21,6 @@ namespace AtomToolsFramework { - AZ_CVAR( - AZ::Color, - ed_cameraSystemOrbitPointColor, - AZ::Color::CreateFromRgba(255, 255, 255, 255), - nullptr, - AZ::ConsoleFunctorFlags::Null, - ""); - AZ_CVAR(float, ed_cameraSystemOrbitPointSize, 0.1f, nullptr, AZ::ConsoleFunctorFlags::Null, ""); - AZ::Transform TransformFromMatrix4x4(const AZ::Matrix4x4& matrix) { const auto rotation = AZ::Matrix3x3::CreateFromMatrix4x4(matrix); @@ -200,14 +191,12 @@ namespace AtomToolsFramework m_cameraViewMatrixChangeHandler = AZ::RPI::ViewportContext::MatrixChangedEvent::Handler(handleCameraChange); m_modularCameraViewportContext->ConnectViewMatrixChangedHandler(m_cameraViewMatrixChangeHandler); - AzFramework::ViewportDebugDisplayEventBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId()); ModularViewportCameraControllerRequestBus::Handler::BusConnect(viewportId); } ModularViewportCameraControllerInstance::~ModularViewportCameraControllerInstance() { ModularViewportCameraControllerRequestBus::Handler::BusDisconnect(); - AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect(); } bool ModularViewportCameraControllerInstance::HandleInputChannelEvent(const AzFramework::ViewportControllerInputEvent& event) @@ -272,7 +261,8 @@ namespace AtomToolsFramework const AZ::Vector3 eulerAngles = AzFramework::EulerAngles(AZ::Matrix3x3::CreateFromTransform(current)); m_camera.m_pitch = eulerAngles.GetX(); m_camera.m_yaw = eulerAngles.GetZ(); - m_camera.m_lookAt = current.GetTranslation(); + m_camera.m_pivot = current.GetTranslation(); + m_camera.m_offset = AZ::Vector3::CreateZero(); m_targetCamera = m_camera; m_modularCameraViewportContext->SetCameraTransform(current); @@ -287,17 +277,6 @@ namespace AtomToolsFramework m_updatingTransformInternally = false; } - void ModularViewportCameraControllerInstance::DisplayViewport( - [[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) - { - if (const float alpha = AZStd::min(-m_camera.m_lookDist / 5.0f, 1.0f); alpha > AZ::Constants::FloatEpsilon) - { - const AZ::Color orbitPointColor = ed_cameraSystemOrbitPointColor; - debugDisplay.SetColor(orbitPointColor.GetR(), orbitPointColor.GetG(), orbitPointColor.GetB(), alpha); - debugDisplay.DrawWireSphere(m_camera.m_lookAt, ed_cameraSystemOrbitPointSize); - } - } - void ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance) { m_cameraMode = CameraMode::Animation; @@ -325,8 +304,8 @@ namespace AtomToolsFramework m_referenceFrameOverride = worldFromLocal; m_targetCamera.m_pitch = 0.0f; m_targetCamera.m_yaw = 0.0f; - m_targetCamera.m_lookAt = AZ::Vector3::CreateZero(); - m_targetCamera.m_lookDist = 0.0f; + m_targetCamera.m_offset = AZ::Vector3::CreateZero(); + m_targetCamera.m_pivot = AZ::Vector3::CreateZero(); m_camera = m_targetCamera; }