diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp index 72434f6037..00591b6c45 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.cpp @@ -284,6 +284,16 @@ namespace AzFramework m_idleCameraInputs.clear(); } + bool Cameras::Exclusive() const + { + return AZStd::any_of( + m_activeCameraInputs.begin(), m_activeCameraInputs.end(), + [](const auto& cameraInput) + { + return cameraInput->Exclusive(); + }); + } + RotateCameraInput::RotateCameraInput(const InputChannelId rotateChannelId) : m_rotateChannelId(rotateChannelId) { @@ -361,7 +371,7 @@ namespace AzFramework }; nextCamera.m_yaw = clampRotation(nextCamera.m_yaw); - // clamp pitch to be +-90 degrees + // clamp pitch to be +/-90 degrees nextCamera.m_pitch = AZ::GetClamp(nextCamera.m_pitch, -AZ::Constants::HalfPi, AZ::Constants::HalfPi); return nextCamera; @@ -428,7 +438,7 @@ namespace AzFramework return nextCamera; } - TranslateCameraInput::TranslationType TranslateCameraInput::translationFromKey(InputChannelId channelId) + TranslateCameraInput::TranslationType TranslateCameraInput::TranslationFromKey(InputChannelId channelId) { if (channelId == CameraTranslateForwardId) { @@ -484,7 +494,7 @@ namespace AzFramework { if (input->m_state == InputChannel::State::Began) { - m_translation |= translationFromKey(input->m_channelId); + m_translation |= TranslationFromKey(input->m_channelId); if (m_translation != TranslationType::Nil) { BeginActivation(); @@ -498,7 +508,7 @@ namespace AzFramework // ensure we don't process end events in the idle state else if (input->m_state == InputChannel::State::Ended && !Idle()) { - m_translation &= ~(translationFromKey(input->m_channelId)); + m_translation &= ~(TranslationFromKey(input->m_channelId)); if (m_translation == TranslationType::Nil) { EndActivation(); diff --git a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h index b25938872a..fd5f47fe72 100644 --- a/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h +++ b/Code/Framework/AzFramework/AzFramework/Viewport/CameraInput.h @@ -23,22 +23,26 @@ namespace AzFramework { - //! Update camera key bindings that can be overridden with AZ console vars (invoke from console to update) + //! Updates camera key bindings that can be overridden with AZ console vars (invoke from console to update). void ReloadCameraKeyBindings(); - //! Return Euler angles (pitch, roll, yaw) for the incoming orientation. + //! Returns Euler angles (pitch, roll, yaw) for the incoming orientation. + //! @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). + //! 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 }; - float m_pitch{ 0.0 }; + 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 - //! View camera transform (v in MVP). + //! View camera transform (V in model-view-projection matrix (MVP)). AZ::Transform View() const; //! World camera transform. AZ::Transform Transform() const; @@ -69,9 +73,10 @@ namespace AzFramework return Transform().GetTranslation(); } + //! Extracts Euler angles (orientation) and translation from the transform and writes the values to the camera. void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform); - //! Generic motion type + //! Generic motion type. template struct MotionEvent { @@ -86,35 +91,44 @@ namespace AzFramework float m_delta; }; + //! Represents an input event which occurs as a discrete change in state (e.g. button down, button up) rather than a continuous stream. + //! @note Such as a key press with a down/up event as opposed to a continuous delta. struct DiscreteInputEvent { InputChannelId m_channelId; //!< Channel type. (e.g. Keyboard key, mouse button or other device input). InputChannel::State m_state; //!< Channel state. (e.g. Begin/update/end event). }; + //! Represents a type-safe union of input events that are handled by the camera system. using InputEvent = AZStd::variant; + //! Base class for all camera behaviors. + //! The core interface consists of: + //! HandleEvents, used to receive and process incoming input events to begin, update and end a behavior. + //! StepCamera, to update the current camera transform (position and orientation). class CameraInput { public: + //! The state of activation the camera input is currently in. + //! State changes of Activation: Idle -> Beginning -> Active -> Ending -> Idle enum class Activation { - Idle, - Begin, - Active, - End + Idle, //!< Camera input is not currently active (initial state and transitioned to from Ending). + Beginning, //!< Camera input is just beginning (transitioned to from Idle). + Active, //!< Camera input is currently active and running (transitioned to from Beginning). + Ending //!< Camera input is ending and will return to idle (transitioned to from Active). }; virtual ~CameraInput() = default; bool Beginning() const { - return m_activation == Activation::Begin; + return m_activation == Activation::Beginning; } bool Ending() const { - return m_activation == Activation::End; + return m_activation == Activation::Ending; } bool Idle() const @@ -129,12 +143,12 @@ namespace AzFramework void BeginActivation() { - m_activation = Activation::Begin; + m_activation = Activation::Beginning; } void EndActivation() { - m_activation = Activation::End; + m_activation = Activation::Ending; } void ContinueActivation() @@ -153,38 +167,56 @@ namespace AzFramework ResetImpl(); } + //! Respond to input events to transition a camera input to active, handle input while running, and restore to idle when input ends. virtual bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) = 0; + //! Use processed input events to update the state of the camera. + //! @note targetCamera is the current target camera at the beginning of an update. The returned camera is the targetCamera + some + //! delta to get to the next camera position and/or orientation. virtual Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) = 0; + //! It is usually possible for one to many camera inputs to be running at the same time (the default), it is however possible to + //! to make a camera input 'exclusive', so only it can run at a time. This implies that all other behaviors must have stopped before + //! it can begin, and no other camera input can run while it is active. virtual bool Exclusive() const { return false; } protected: + //! Handle any state reset that may be required for the camera input (optional). virtual void ResetImpl() { } private: - Activation m_activation = Activation::Idle; + Activation m_activation = Activation::Idle; //!< Default all camera inputs to the idle state. }; //! Properties to use to configure behavior across all types of camera. struct CameraProps { - AZStd::function m_rotateSmoothnessFn; //!< Rotate smoothing value (useful approx range 3-6, higher values give sharper feel). - AZStd::function m_translateSmoothnessFn; //!< Translate smoothing value (useful approx range 3-6, higher values give sharper feel). + AZStd::function + m_rotateSmoothnessFn; //!< Rotate smoothing value (useful approx range 3-6, higher values give sharper feel). + AZStd::function + m_translateSmoothnessFn; //!< Translate smoothing value (useful approx range 3-6, higher values give sharper feel). }; + //! An interpolation function to smoothly interpolate all camera properties from currentCamera to targetCamera. + //! The camera returned will be some value between current and target camera. + //! @note The rate of interpolation can be customized with CameraProps. Camera SmoothCamera(const Camera& currentCamera, const Camera& targetCamera, const CameraProps& cameraProps, float deltaTime); + //! Manages a list of camera inputs. + //! By default all camera inputs are added to the idle list, when a camera activates it will be added to the active list, when it + //! deactivates it will be returned to the idle list. class Cameras { public: bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta); Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime); + //! Add a camera input (behavior) to run in this set of camera inputs. + //! The camera inputs added here will determine the overall behavior of the camera. void AddCamera(AZStd::shared_ptr cameraInput); //! Reset the state of all cameras. void Reset(); @@ -195,20 +227,11 @@ namespace AzFramework bool Exclusive() const; private: - AZStd::vector> m_activeCameraInputs; - AZStd::vector> m_idleCameraInputs; + AZStd::vector> m_activeCameraInputs; //!< Active camera inputs updating the camera (empty initially). + AZStd::vector> + m_idleCameraInputs; //!< Idle camera inputs not contributing to the update (filled initially). }; - inline bool Cameras::Exclusive() const - { - return AZStd::any_of( - m_activeCameraInputs.begin(), m_activeCameraInputs.end(), - [](const auto& cameraInput) - { - return cameraInput->Exclusive(); - }); - } - //! Responsible for updating a series of cameras given various inputs. class CameraSystem { @@ -216,13 +239,14 @@ namespace AzFramework bool HandleEvents(const InputEvent& event); Camera StepCamera(const Camera& targetCamera, float deltaTime); - Cameras m_cameras; + 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. float m_scrollDelta = 0.0f; //!< The delta used for dolly/movement (translation) - one dimensional. }; + //! A camera input to handle motion deltas that can rotate or orbit the camera. class RotateCameraInput : public CameraInput { public: @@ -237,24 +261,28 @@ namespace AzFramework AZStd::function m_invertYawFn; private: - InputChannelId m_rotateChannelId; - ClickDetector m_clickDetector; + InputChannelId m_rotateChannelId; //!< Input channel to begin the rotate camera input. + ClickDetector m_clickDetector; //!< Used to determine when a sufficient motion delta has occurred to begin the input. }; + //! Axes to use while panning the camera. struct PanAxes { AZ::Vector3 m_horizontalAxis; AZ::Vector3 m_verticalAxis; }; + //! PanAxes build function that will return a pair of pan axes depending on the camera orientation. using PanAxesFn = AZStd::function; + //! PanAxes to use while in 'look' camera behavior (free look). inline PanAxes LookPan(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); return { orientation.GetBasisX(), orientation.GetBasisZ() }; } + //! PanAxes to use while in 'orbit' camera behavior. inline PanAxes OrbitPan(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -269,6 +297,7 @@ namespace AzFramework return { basisX, basisY }; } + //! A camera input to handle motion deltas that can pan the camera (translate in two axes). class PanCameraInput : public CameraInput { public: @@ -283,12 +312,14 @@ namespace AzFramework AZStd::function m_invertPanYFn; private: - PanAxesFn m_panAxesFn; - InputChannelId m_panChannelId; + PanAxesFn m_panAxesFn; //!< Builder for the particular pan axes (provided in the constructor). + InputChannelId m_panChannelId; //!< Input channel to begin the pan camera input. }; + //! Axes to use while translating the camera. using TranslationAxesFn = AZStd::function; + //! TranslationAxes to use while in 'look' camera behavior (free look). inline AZ::Matrix3x3 LookTranslation(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -300,6 +331,7 @@ namespace AzFramework return AZ::Matrix3x3::CreateFromColumns(basisX, basisY, basisZ); } + //! TranslationAxes to use while in 'orbit' camera behavior. inline AZ::Matrix3x3 OrbitTranslation(const Camera& camera) { const AZ::Matrix3x3 orientation = camera.Rotation(); @@ -315,6 +347,7 @@ namespace AzFramework return AZ::Matrix3x3::CreateFromColumns(basisX, basisY, basisZ); } + //! A camera input to handle discrete events that can translate the camera (translate in three axes). class TranslateCameraInput : public CameraInput { public: @@ -329,6 +362,7 @@ namespace AzFramework AZStd::function m_boostMultiplierFn; private: + //! The type of translation the camera input is performing (multiple may be active at once). enum class TranslationType { // clang-format off @@ -383,13 +417,15 @@ namespace AzFramework return static_cast(~static_cast>(lhs)); } - static TranslationType translationFromKey(InputChannelId channelId); + //! Converts from a generic input channel id to a concrete translation type (based on the user's key mappings). + static TranslationType TranslationFromKey(InputChannelId channelId); - TranslationType m_translation = TranslationType::Nil; - TranslationAxesFn m_translationAxesFn; - bool m_boost = false; + TranslationType m_translation = TranslationType::Nil; //!< Types of translation the camera input is under. + TranslationAxesFn m_translationAxesFn; //!< Builder for translation axes. + 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 { public: @@ -402,6 +438,7 @@ namespace AzFramework AZStd::function m_scrollSpeedFn; }; + //! A camera input to handle motion deltas that can modify the camera look distance. class OrbitDollyCursorMoveCameraInput : public CameraInput { public: @@ -417,6 +454,7 @@ namespace AzFramework InputChannelId m_dollyChannelId; }; + //! A camera input to handle discrete scroll events that can scroll (translate) the camera along its forward axis. class ScrollTranslationCameraInput : public CameraInput { public: @@ -429,6 +467,8 @@ namespace AzFramework AZStd::function m_scrollSpeedFn; }; + //! 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 { public: @@ -439,13 +479,13 @@ namespace AzFramework Camera StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, float scrollDelta, float deltaTime) override; bool Exclusive() const override; - Cameras m_orbitCameras; + Cameras m_orbitCameras; //!< 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); private: - LookAtFn m_lookAtFn; + LookAtFn m_lookAtFn; //!< The look-at behavior to use for this orbit camera (how is the look-at point calculated/retrieved). }; inline void OrbitCameraInput::SetLookAtFn(const LookAtFn& lookAtFn)