Kaynağa Gözat

Implement Movement in XR via Controller Input (#48)

* Organize PAL functions in OpenXRVk Gem

- Recent android environment and Oculus Touch input code was produced in
  PAL folders, but their implementations were a bit unfinished.
- This adds missing implementations for platforms where XR is
  effectively disabled.
- This also better hides functionality behind PAL and uses better
  abstractions to reduce repeated code.

Signed-off-by: amzn-phist <[email protected]>

* First pass at a camera movement component

- Similar to the FlyCameraInput component, this hooks up XR Controller
  inputs to translate the camera position around.
- First implementation of basic movement controls, but still needs
  tweaks.

Signed-off-by: amzn-phist <[email protected]>

* Setting up code for Debug Draw functions

- A Cvar is added to enable/disable the debug draw functionality.
- Stubbed out the functions to draw.

Signed-off-by: amzn-phist <[email protected]>

* Update the XR_Office level

- replaces the FlyCameraInput component (actually disables it) with the
  XRCameraMovement component.

Signed-off-by: amzn-phist <[email protected]>

* Get first pass of controller debug draw working

- Drawing data for both controllers, including default, touched, and
  pressed states.
- When thumb-sticks are pushed, their values are also displayed.
- Also hooked up position and orientation to be written to the raw input
  data structure, it was missed before.
- Currently draws axes where the controllers are but it seems to be in
  local space and not view space.

Signed-off-by: amzn-phist <[email protected]>

* Updating the XR_Office level

- No changes, just re-saved.

Signed-off-by: amzn-phist <[email protected]>

* Expand the debug draw functionality

- Draw xyz-axes where controllers are (still a bit buggy)
- Draw position and orientation data

Signed-off-by: amzn-phist <[email protected]>

* Updates the xr camera movement controller

- Movement in X and Y is based off of the current view's basis.  So if
  you yaw/pitch/roll your head, then the left thumb-stick's values will
  push the position based off the right and forward vectors of the head.
- Removed the rotation input, not going to be needed.

Signed-off-by: amzn-phist <[email protected]>

* Clean up unused code in the movement gem

Signed-off-by: amzn-phist <[email protected]>

* Implemented button combo to toggle debug draw cvar

- Simultaneously clicking the left controller's Menu button and pulling
  the left Trigger to full will toggle the debug draw Cvar.
- Simplified code that checks for digital button states and made
  replacements in various spots.

Signed-off-by: amzn-phist <[email protected]>

* Address first round of feedback

Signed-off-by: amzn-phist <[email protected]>

* Removes log spam when device goes idle

- Changed a warning to simple return when xrSyncActions is unsuccessful.
- Reduces log spam when setting down the headset/controllers.

Signed-off-by: amzn-phist <[email protected]>

* Address more PR feedback

- Removed extern cvar decl.
- Removed comments from component interface.
- Fixed #include of a cpp.
- Early return of debug draw routine.

Signed-off-by: amzn-phist <[email protected]>

* Make a class static function a private cpp static

- Doesn't need to be declared on the class' header.

Signed-off-by: amzn-phist <[email protected]>

* Addressing more feedback

- Updated one of the PAL implementations (input device) to move the
  function to cpp files and removed unnecessary files.

Signed-off-by: amzn-phist <[email protected]>

* Addresses PR feedback

- Cleanup

Signed-off-by: amzn-phist <[email protected]>

* Addresses more PR feedback about PAL

- Fixed the remaining PAL files by adding a private header for the
  common platform functions.
- The implementations under PAL are in cpp files now, cleaned up the
  rest of the changes.

Signed-off-by: amzn-phist <[email protected]>

* Address minor feedback

Signed-off-by: amzn-phist <[email protected]>

Signed-off-by: amzn-phist <[email protected]>
amzn-phist 2 yıl önce
ebeveyn
işleme
6eae1b57c8
33 değiştirilmiş dosya ile 770 ekleme ve 135 silme
  1. 25 1
      Gems/OpenXRVk/Code/Include/OpenXRVk/InputDeviceXRController.h
  2. 9 0
      Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/Mac/OpenXRVk_Platform.h
  3. 1 0
      Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/Mac/platform_private_mac_files.cmake
  4. 9 0
      Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/iOS/OpenXRVk_Platform.h
  5. 1 0
      Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/iOS/platform_private_ios_files.cmake
  6. 341 4
      Gems/OpenXRVk/Code/Source/InputDeviceXRController.cpp
  7. 28 0
      Gems/OpenXRVk/Code/Source/OpenXRVkCommon.h
  8. 1 1
      Gems/OpenXRVk/Code/Source/OpenXRVkDevice.cpp
  9. 32 8
      Gems/OpenXRVk/Code/Source/OpenXRVkInput.cpp
  10. 1 1
      Gems/OpenXRVk/Code/Source/OpenXRVkInstance.cpp
  11. 3 1
      Gems/OpenXRVk/Code/Source/OpenXRVkModule.cpp
  12. 5 1
      Gems/OpenXRVk/Code/Source/OpenXRVkSystemComponent.cpp
  13. 5 5
      Gems/OpenXRVk/Code/Source/Platform/Android/OpenXRVkCommon_Android.cpp
  14. 0 18
      Gems/OpenXRVk/Code/Source/Platform/Android/OpenXRVk_Traits_Android.h
  15. 2 1
      Gems/OpenXRVk/Code/Source/Platform/Android/platform_private_android_files.cmake
  16. 23 0
      Gems/OpenXRVk/Code/Source/Platform/Common/Default/InputDeviceXRController_Default.cpp
  17. 0 8
      Gems/OpenXRVk/Code/Source/Platform/Common/Default/OculusTouch_Default.h
  18. 20 0
      Gems/OpenXRVk/Code/Source/Platform/Common/Unimplemented/InputDeviceXRController_Unimplemented.cpp
  19. 4 4
      Gems/OpenXRVk/Code/Source/Platform/Common/Unimplemented/OpenXRVkCommon_Unimplemented.cpp
  20. 0 18
      Gems/OpenXRVk/Code/Source/Platform/Linux/OpenXRVk_Traits_Linux.h
  21. 2 1
      Gems/OpenXRVk/Code/Source/Platform/Linux/platform_private_linux_files.cmake
  22. 1 1
      Gems/OpenXRVk/Code/Source/Platform/Mac/OpenXRVk_Traits_Mac.h
  23. 2 0
      Gems/OpenXRVk/Code/Source/Platform/Mac/platform_private_mac_files.cmake
  24. 0 29
      Gems/OpenXRVk/Code/Source/Platform/Windows/OpenXRVk_Traits_Windows.cpp
  25. 0 18
      Gems/OpenXRVk/Code/Source/Platform/Windows/OpenXRVk_Traits_Windows.h
  26. 2 1
      Gems/OpenXRVk/Code/Source/Platform/Windows/platform_private_windows_files.cmake
  27. 1 1
      Gems/OpenXRVk/Code/Source/Platform/iOS/OpenXRVk_Traits_iOS.h
  28. 2 0
      Gems/OpenXRVk/Code/Source/Platform/iOS/platform_private_ios_files.cmake
  29. 166 0
      Gems/OpenXRVk/Code/Source/XRCameraMovementComponent.cpp
  30. 59 0
      Gems/OpenXRVk/Code/Source/XRCameraMovementComponent.h
  31. 3 0
      Gems/OpenXRVk/Code/openxrvk_private_common_files.cmake
  32. 1 1
      Projects/OpenXRTest/Gem/Source/OpenXRTestSystemComponent.h
  33. 21 12
      Projects/OpenXRTest/Levels/XR_Office/XR_Office.prefab

+ 25 - 1
Gems/OpenXRVk/Code/Include/OpenXRVk/InputDeviceXRController.h

@@ -17,7 +17,9 @@
 #include <AzFramework/Input/Channels/InputChannelQuaternion.h>
 #include <AzFramework/Input/Devices/InputDevice.h>
 
-////////////////////////////////////////////////////////////////////////////////////////////////////
+#include <AzFramework/Entity/EntityDebugDisplayBus.h>
+
+
 namespace AzFramework
 {
     ////////////////////////////////////////////////////////////////////////////////////////////////
@@ -28,6 +30,7 @@ namespace AzFramework
     class InputDeviceXRController
         : public InputDevice
         , public InputHapticFeedbackRequestBus::Handler
+        , public AzFramework::DebugDisplayEventBus::Handler
     {
     public:
         ////////////////////////////////////////////////////////////////////////////////////////////
@@ -236,6 +239,11 @@ namespace AzFramework
             virtual AZStd::string GetLeftHandSubPath() const = 0;
             virtual AZStd::string GetRightHandSubPath() const = 0;
 
+            ////////////////////////////////////////////////////////////////////////////////////////
+            //! Register a callback function for this implementation to call
+            //! This callback is called during tick updates and allows for platform code to run
+            //! updates to refresh state of the controller data.
+            //! @param callbackFn The callback function
             using TickCallbackFn = AZStd::function<void()>;
             virtual void RegisterTickCallback(TickCallbackFn callbackFn) = 0;
 
@@ -281,6 +289,10 @@ namespace AzFramework
                 //! Reset the raw xr controller data
                 void Reset();
 
+                ////////////////////////////////////////////////////////////////////////////////////
+                //! Gets a digital button's current state
+                bool GetDigitalButtonState(const InputChannelId& channelId) const;
+
                 ////////////////////////////////////////////////////////////////////////////////////
                 //! Get the left trigger value adjusted for the dead zone and normalized
                 //! @return The adjusted left trigger value
@@ -413,6 +425,18 @@ namespace AzFramework
         Implementation* GetImplementation() const;
 
     protected:
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        // AzFramework::DebugDisplayEventBus interface
+        void DrawGlobalDebugInfo() override;
+
+        ////////////////////////////////////////////////////////////////////////////////////////////
+        //! Helper routine that checks for a specific controller input combo and will toggle the debug draw.
+        //! Allows users to quickly display/hide the debug information for the xr controllers without having
+        //! to type anything into a console.
+        //! Currently bound to: Button::Menu + Trigger::LTrigger
+        //! That is, hold the left controller's Menu button and squeeze the left trigger fully.
+        void CheckDebugDrawCheat() const;
+
         static constexpr float s_thumbStickMaxValue{ 1.f };
         static constexpr float s_thumbStickMinValue{ -1.f };
         static constexpr float s_thumbStickCenterValue{ 0.f };

+ 9 - 0
Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/Mac/OpenXRVk_Platform.h

@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once

+ 1 - 0
Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/Mac/platform_private_mac_files.cmake

@@ -7,4 +7,5 @@
 #
 
 set(FILES
+    OpenXRVk_Platform.h
 )

+ 9 - 0
Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/iOS/OpenXRVk_Platform.h

@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once

+ 1 - 0
Gems/OpenXRVk/Code/Include/OpenXRVk/Platform/iOS/platform_private_ios_files.cmake

@@ -7,4 +7,5 @@
 #
 
 set(FILES
+    OpenXRVk_Platform.h
 )

+ 341 - 4
Gems/OpenXRVk/Code/Source/InputDeviceXRController.cpp

@@ -11,11 +11,26 @@
 #include <AzCore/RTTI/BehaviorContext.h>
 #include <AzFramework/Input/Utils/AdjustAnalogInputForDeadZone.h>
 
-////////////////////////////////////////////////////////////////////////////////////////////////////
+// Debug Draw
+#include <AzCore/Console/IConsole.h>
+#include <Atom/RPI.Public/ViewportContext.h>
+#include <Atom/RPI.Public/ViewportContextBus.h>
+
+namespace OpenXRVk
+{
+    // Cvar to enable/disable debug drawing of xr controller data on screen.
+    // No "on change" function defined here, just read the state of the bool
+    // elsewhere in the draw function.
+    AZ_CVAR(bool, xr_DebugDrawInput, 0,
+        nullptr, AZ::ConsoleFunctorFlags::Null,
+        "Turn off/on debug drawing of XR Input state");
+
+} // namespace OpenXRVk
+
+
 namespace AzFramework
 {
     ////////////////////////////////////////////////////////////////////////////////////////////////
-    // static
     bool InputDeviceXRController::IsXRControllerDevice(const InputDeviceId& inputDeviceId)
     {
         // Only need to check the name (crc) to check the device is an xr controller type.
@@ -160,11 +175,17 @@ namespace AzFramework
 
         // Connect to haptic feedback request bus
         InputHapticFeedbackRequestBus::Handler::BusConnect(GetInputDeviceId());
+
+        // Debug Draw
+        DebugDisplayEventBus::Handler::BusConnect();
     }
 
     ////////////////////////////////////////////////////////////////////////////////////////////////
     InputDeviceXRController::~InputDeviceXRController()
     {
+        // Debug Draw
+        DebugDisplayEventBus::Handler::BusDisconnect();
+
         // Disconnect from haptic feedback request bus
         InputHapticFeedbackRequestBus::Handler::BusDisconnect(GetInputDeviceId());
 
@@ -235,6 +256,7 @@ namespace AzFramework
         return m_impl.get();
     }
 
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
     //! InputDeviceXRController::Implementation
     ////////////////////////////////////////////////////////////////////////////////////////////////
@@ -289,6 +311,16 @@ namespace AzFramework
         m_rightOrientationState = AZ::Quaternion::CreateIdentity();
     }
 
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    bool InputDeviceXRController::Implementation::RawXRControllerState::GetDigitalButtonState(const InputChannelId& channelId) const
+    {
+        if (auto it = m_buttonIdsToBitMasks.find(channelId); it != m_buttonIdsToBitMasks.end())
+        {
+            return (m_digitalButtonStates & it->second) != 0;
+        }
+        return false;
+    }
+
     ////////////////////////////////////////////////////////////////////////////////////////////////
     float InputDeviceXRController::Implementation::RawXRControllerState::GetLeftTriggerAdjustedForDeadZoneAndNormalized() const
     {
@@ -398,8 +430,6 @@ namespace AzFramework
         m_inputDevice.m_controllerPositionChannelsById[xrc::ControllerPosePosition::RPos]
             ->ProcessRawInputEvent(rawControllerState.m_rightPositionState);
 
-        // TBD: Process Velocity and Acceleration...
-
         // Orientation update...
         m_inputDevice.m_controllerOrientationChannelsById[xrc::ControllerPoseOrientation::LOrient]
             ->ProcessRawInputEvent(rawControllerState.m_leftOrientationState);
@@ -419,4 +449,311 @@ namespace AzFramework
         return m_inputDevice.GetInputDeviceId().GetIndex();
     }
 
+
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    // Debug Draw Related Functions
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if !defined(AZ_RELEASE_BUILD)
+
+    static AZ::Transform GetCameraTransformFromCurrentView()
+    {
+        if (const auto viewportContextMgr = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
+            viewportContextMgr != nullptr)
+        {
+            if (const AZ::RPI::ViewportContextPtr viewportContext = viewportContextMgr->GetDefaultViewportContext();
+                viewportContext != nullptr)
+            {
+                if (const AZ::RPI::ViewPtr view = viewportContext->GetDefaultView();
+                    view != nullptr)
+                {
+                    return view->GetCameraTransform();
+                }
+            }
+        }
+        return AZ::Transform::CreateIdentity();
+    }
+
+    static void DrawControllerAxes(DebugDisplayRequests& debugDisplay, const AZ::Vector3& position, const AZ::Quaternion& orientation)
+    {
+        static const AZ::Color axisColorX(1.f, 0.f, 0.f, 0.9f);
+        static const AZ::Color axisColorY(0.f, 1.f, 0.f, 0.9f);
+        static const AZ::Color axisColorZ(0.f, 0.f, 1.f, 0.9f);
+
+        const auto cameraTransform = GetCameraTransformFromCurrentView();
+        const AZ::Vector3& cameraPosition = cameraTransform.GetTranslation();
+        const AZ::Vector3 controllerPosition = cameraPosition + position;
+
+        const AZ::Transform controllerTransform = AZ::Transform::CreateFromQuaternionAndTranslation(orientation, controllerPosition);
+        debugDisplay.SetColor(axisColorX);
+        debugDisplay.DrawLine(controllerPosition, controllerPosition + controllerTransform.GetBasisX());
+        debugDisplay.SetColor(axisColorY);
+        debugDisplay.DrawLine(controllerPosition, controllerPosition + controllerTransform.GetBasisY());
+        debugDisplay.SetColor(axisColorZ);
+        debugDisplay.DrawLine(controllerPosition, controllerPosition + controllerTransform.GetBasisZ());
+    }
+
+#endif // !AZ_RELEASE_BUILD
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::CheckDebugDrawCheat() const
+    {
+#if !defined(AZ_RELEASE_BUILD)
+        // This looks for specific controller input and will toggle the debug draw cvar.
+        const auto& rawControllerData = m_impl->GetRawState();
+        using xrc = InputDeviceXRController;
+        static bool cheatWasPressed = false;
+
+        // Menu button + Left Trigger pulled past 0.9 will toggle.
+        // To avoid button bounce, block re-toggle until the menu button is released.
+        const bool menuPressed = rawControllerData.GetDigitalButtonState(xrc::Button::Menu);
+        const float leftTrigger = rawControllerData.GetLeftTriggerAdjustedForDeadZoneAndNormalized();
+        if (menuPressed)
+        {
+            if (!cheatWasPressed && leftTrigger > 0.9f)
+            {
+                cheatWasPressed = true;
+                OpenXRVk::xr_DebugDrawInput = !OpenXRVk::xr_DebugDrawInput;
+            }
+        }
+        else
+        {
+            cheatWasPressed = false;
+        }
+#endif // !AZ_RELEASE_BUILD
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    void InputDeviceXRController::DrawGlobalDebugInfo()
+    {
+#if !defined(AZ_RELEASE_BUILD)
+        CheckDebugDrawCheat();
+
+        if (!OpenXRVk::xr_DebugDrawInput)
+        {
+            return;
+        }
+
+        DebugDisplayRequestBus::BusPtr debugDisplayBus;
+        DebugDisplayRequestBus::Bind(debugDisplayBus, g_defaultSceneEntityDebugDisplayId);
+        DebugDisplayRequests* debugDisplay{ DebugDisplayRequestBus::FindFirstHandler(debugDisplayBus) };
+        if (!debugDisplay || !IsSupported())
+        {
+            return;
+        }
+
+        // Save previous draw state
+        const AZ::u32 oldDrawState{ debugDisplay->GetState() };
+
+        // ... draw data to the screen ...
+        const auto& rawControllerData = m_impl->GetRawState();
+        DrawControllerAxes(*debugDisplay, rawControllerData.m_leftPositionState, rawControllerData.m_leftOrientationState);
+        DrawControllerAxes(*debugDisplay, rawControllerData.m_rightPositionState, rawControllerData.m_rightOrientationState);
+
+        float drawX = 20.f;     // current draw X
+        float drawY = 20.f;     // current draw Y
+        constexpr float textSize = 0.8f;
+        constexpr float lineHeight = 15.f;
+
+        const AZ::Color whiteColor{ 1.f, 1.f, 1.f, 1.f };
+        const AZ::Color pressedColor{ 0.f, 1.f, 0.2f, 1.f };
+        const AZ::Color touchedColor{ 0.7f, 0.5f, 0.2f, 1.f };
+        const AZ::Color defaultColor{ 0.2f, 0.2f, 0.2f, 0.8f };
+
+        auto printButtonWithTouchState = [&](const InputChannelId& buttonChannel,
+            const InputChannelId& touchedButtonChannel, const char* buttonText)
+        {
+            AZStd::string text{ buttonText };
+            if (rawControllerData.GetDigitalButtonState(buttonChannel))
+            {
+                text.append(" Pressed");
+                debugDisplay->SetColor(pressedColor);
+            }
+            else if (rawControllerData.GetDigitalButtonState(touchedButtonChannel))
+            {
+                text.append(" Touched");
+                debugDisplay->SetColor(touchedColor);
+            }
+            else
+            {
+                debugDisplay->SetColor(defaultColor);
+            }
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, text.c_str());
+
+            drawY += lineHeight;
+        };
+
+        auto printButtonState = [&](const InputChannelId& buttonChannel, const char* buttonText)
+        {
+            AZStd::string text{ buttonText };
+            if (rawControllerData.GetDigitalButtonState(buttonChannel))
+            {
+                text.append(" Pressed");
+                debugDisplay->SetColor(pressedColor);
+            }
+            else
+            {
+                debugDisplay->SetColor(defaultColor);
+            }
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, text.c_str());
+
+            drawY += lineHeight;
+        };
+
+        auto printButtonTouchOnlyState = [&](const InputChannelId& touchChannel, const char* buttonText)
+        {
+            AZStd::string text{ buttonText };
+            if (rawControllerData.GetDigitalButtonState(touchChannel))
+            {
+                text.append(" Touched");
+                debugDisplay->SetColor(touchedColor);
+            }
+            else
+            {
+                debugDisplay->SetColor(defaultColor);
+            }
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, text.c_str());
+
+            drawY += lineHeight;
+        };
+
+        auto printAnalogWithTouchState = [&](const InputChannelId& touchedChannel, const char* analogText, float value)
+        {
+            AZStd::string text{ analogText };
+            if (!AZ::IsClose(value, 0.f))
+            {
+                text.append(AZStd::string::format(" Pressed: %.2f", value));
+                debugDisplay->SetColor(pressedColor);
+            }
+            else if (rawControllerData.GetDigitalButtonState(touchedChannel))
+            {
+                text.append(" Touched");
+                debugDisplay->SetColor(touchedColor);
+            }
+            else
+            {
+                debugDisplay->SetColor(defaultColor);
+            }
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, text.c_str());
+
+            drawY += lineHeight;
+        };
+
+        auto printAnalogState = [&](const char* analogText, float value)
+        {
+            AZStd::string text{ analogText };
+            if (!AZ::IsClose(value, 0.f))
+            {
+                text.append(AZStd::string::format(" = %.2f", value));
+                debugDisplay->SetColor(pressedColor);
+            }
+            else
+            {
+                debugDisplay->SetColor(defaultColor);
+            }
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, text.c_str());
+
+            drawY += lineHeight;
+        };
+
+        auto print2DThumbStickWithTouchState = [&](const InputChannelId& touchedChannel, const char* thumbStickText, float xvalue, float yvalue)
+        {
+            AZStd::string text{ thumbStickText };
+            if (!AZ::IsClose(xvalue, 0.f) || !AZ::IsClose(yvalue, 0.f))
+            {
+                text.append(AZStd::string::format(" Pressed: (%.2f, %.2f)", xvalue, yvalue));
+                debugDisplay->SetColor(pressedColor);
+            }
+            else if (rawControllerData.GetDigitalButtonState(touchedChannel))
+            {
+                text.append(" Touched");
+                debugDisplay->SetColor(touchedColor);
+            }
+            else
+            {
+                debugDisplay->SetColor(defaultColor);
+            }
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, text.c_str());
+
+            drawY += lineHeight;
+        };
+
+        auto printVector3 = [&](const AZ::Vector3& vec, const char* vectorText)
+        {
+            AZStd::string str{ AZStd::string::format("%s = (%.2f, %.2f, %.2f)", vectorText, vec.GetX(), vec.GetY(), vec.GetZ()) };
+            debugDisplay->SetColor(whiteColor);
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, str.c_str());
+
+            drawY += lineHeight;
+        };
+
+        auto printMatrix3x4 = [&](const AZ::Matrix3x4& matx, const char* matrixText)
+        {
+            debugDisplay->SetColor(whiteColor);
+            AZStd::string str{ AZStd::string::format("%s:", matrixText) };
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, str.c_str());
+            drawY += lineHeight;
+
+            AZ::Vector3 col0, col1, col2, col3;
+            matx.GetColumns(&col0, &col1, &col2, &col3);
+            str = AZStd::string::format("    | %.2f    %.2f    %.2f    %.2f |", col0.GetX(), col1.GetX(), col2.GetX(), col3.GetX());
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, str.c_str());
+            drawY += lineHeight;
+            str = AZStd::string::format("    | %.2f    %.2f    %.2f    %.2f |", col0.GetY(), col1.GetY(), col2.GetY(), col3.GetY());
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, str.c_str());
+            drawY += lineHeight;
+            str = AZStd::string::format("    | %.2f    %.2f    %.2f    %.2f |", col0.GetZ(), col1.GetZ(), col2.GetZ(), col3.GetZ());
+            debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, str.c_str());
+            drawY += lineHeight;
+        };
+
+
+        using xrc = InputDeviceXRController;
+
+        // Left controller...
+        debugDisplay->SetColor(whiteColor);
+        debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, "Left XR Controller");
+        drawY += lineHeight;
+
+        printButtonWithTouchState(xrc::Button::X, xrc::Button::TX, "X");
+        printButtonWithTouchState(xrc::Button::Y, xrc::Button::TY, "Y");
+        printButtonState(xrc::Button::L3, "L3");
+        printButtonState(xrc::Button::Menu, "Menu");
+        printButtonTouchOnlyState(xrc::Button::TLRest, "L ThumbRest");
+        printAnalogWithTouchState(xrc::Button::TLTrig, "L Trigger", rawControllerData.m_leftTriggerState);
+        printAnalogState("L Grip", rawControllerData.m_leftGripState);
+        print2DThumbStickWithTouchState(xrc::Button::TLStick, "L Thumb-Stick",
+            rawControllerData.m_leftThumbStickXState, rawControllerData.m_leftThumbStickYState);
+
+        drawY += (2.f * lineHeight);
+
+        // Right controller...
+        debugDisplay->SetColor(whiteColor);
+        debugDisplay->Draw2dTextLabel(drawX, drawY, textSize, "Right XR Controller");
+        drawY += lineHeight;
+
+        printButtonWithTouchState(xrc::Button::A, xrc::Button::TA, "A");
+        printButtonWithTouchState(xrc::Button::B, xrc::Button::TB, "B");
+        printButtonState(xrc::Button::R3, "R3");
+        printButtonState(xrc::Button::Home, "Home");
+        printButtonTouchOnlyState(xrc::Button::TRRest, "R ThumbRest");
+        printAnalogWithTouchState(xrc::Button::TLTrig, "R Trigger", rawControllerData.m_rightTriggerState);
+        printAnalogState("R Grip", rawControllerData.m_rightGripState);
+        print2DThumbStickWithTouchState(xrc::Button::TRStick, "R Thumb-Stick",
+            rawControllerData.m_rightThumbStickXState, rawControllerData.m_rightThumbStickYState);
+
+        drawY += (2.f * lineHeight);
+
+        // Positions and Orientation
+        printVector3(rawControllerData.m_leftPositionState, "Left Controller Position");
+        printMatrix3x4(AZ::Matrix3x4::CreateFromQuaternion(rawControllerData.m_leftOrientationState), "Left Controller Orientation");
+        printVector3(rawControllerData.m_rightPositionState, "Right Controller Position");
+        printMatrix3x4(AZ::Matrix3x4::CreateFromQuaternion(rawControllerData.m_rightOrientationState), "Right Controller Orientation");
+
+        // Restore previous state
+        debugDisplay->SetState(oldDrawState);
+#endif // !AZ_RELEASE_BUILD
+    }
+
 } // namespace AzFramework

+ 28 - 0
Gems/OpenXRVk/Code/Source/OpenXRVkCommon.h

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+namespace OpenXRVk::Platform
+{
+    //! Initializes the XR loader for this platform.
+    bool OpenXRInitializeLoader();
+
+    //! Called when the device is beginning a frame for processing.
+    //! @note This function is called from the thread related to the presentation queue.
+    void OpenXRBeginFrameInternal();
+
+    //! Called when the device is ending a frame for processing.
+    //! @note This function is called from the thread related to the presentation queue.
+    void OpenXREndFrameInternal();
+
+    //! Called after the EndFrame has been executed.
+    //! @note This function is called from the main thread.
+    void OpenXRPostFrameInternal();
+
+} // namespace OpenXRVk::Platform

+ 1 - 1
Gems/OpenXRVk/Code/Source/OpenXRVkDevice.cpp

@@ -12,7 +12,7 @@
 #include <OpenXRVk/OpenXRVkSwapChain.h>
 #include <OpenXRVk/OpenXRVkSpace.h>
 #include <OpenXRVk/OpenXRVkUtils.h>
-#include <OpenXRVk_Traits_Platform.h>
+#include <OpenXRVkCommon.h>
 #include <Atom/RHI.Reflect/Vulkan/XRVkDescriptors.h>
 #include <AzCore/Casting/numeric_cast.h>
 

+ 32 - 8
Gems/OpenXRVk/Code/Source/OpenXRVkInput.cpp

@@ -14,8 +14,6 @@
 #include <OpenXRVk/OpenXRVkUtils.h>
 #include <AzCore/Casting/numeric_cast.h>
 
-#include <../Common/Default/OculusTouch_Default.h>
-
 namespace OpenXRVk
 {
     XR::Ptr<Input> Input::Create()
@@ -241,7 +239,12 @@ namespace OpenXRVk
         syncInfo.activeActionSets = &activeActionSet;
 
         XrResult result = xrSyncActions(xrSession, &syncInfo);
-        WARN_IF_UNSUCCESSFUL(result);
+        if (result != XR_SUCCESS)
+        {
+            // This will hit when the device gets put down / goes idle.
+            // So to avoid spam, just return here.
+            return;
+        }
 
         using namespace AzFramework;
         using xrc = InputDeviceXRController;
@@ -344,7 +347,7 @@ namespace OpenXRVk
 
             result = xrGetActionStatePose(xrSession, &getInfo, &poseState);
             WARN_IF_UNSUCCESSFUL(result);
-            m_handActive[static_cast<uint32_t>(hand)] = poseState.isActive;
+            m_handActive[static_cast<AZ::u32>(hand)] = poseState.isActive;
 
             LocateControllerSpace(device->GetPredictedDisplayTime(), session->GetXrSpace(OpenXRVk::SpaceType::View), static_cast<AZ::u32>(hand));
         }
@@ -357,6 +360,27 @@ namespace OpenXRVk
                                   session->GetXrSpace(OpenXRVk::SpaceType::View), spaceType);
         }
 
+        // XR to AZ vector conversion...
+        // Goes from y-up to z-up configuration (keeping Right Handed system)
+        const auto convertVector3 = [](const XrVector3f& xrVec3) -> AZ::Vector3
+        {
+            return AZ::Vector3{ xrVec3.x, -xrVec3.z, xrVec3.y };
+        };
+
+        // XR to AZ quaternion conversion...
+        // Goes from y-up to z-up configuration (keeping Right Handed system)
+        const auto convertQuat = [](const XrQuaternionf& xrQuat) -> AZ::Quaternion
+        {
+            return AZ::Quaternion{ xrQuat.x, -xrQuat.z, xrQuat.y, xrQuat.w };
+        };
+
+        rawControllerData.m_leftPositionState = convertVector3(m_handSpaceLocation[static_cast<AZ::u32>(XR::Side::Left)].pose.position);
+        rawControllerData.m_rightPositionState = convertVector3(m_handSpaceLocation[static_cast<AZ::u32>(XR::Side::Right)].pose.position);
+
+        rawControllerData.m_leftOrientationState = convertQuat(m_handSpaceLocation[static_cast<AZ::u32>(XR::Side::Left)].pose.orientation);
+        rawControllerData.m_rightOrientationState = convertQuat(m_handSpaceLocation[static_cast<AZ::u32>(XR::Side::Right)].pose.orientation);
+
+        // Check if the Quit (Home) button was pressed this sync...
         const bool quitPressed = GetButtonState(InputDeviceXRController::Button::Home);
         if (quitPressed && !m_wasQuitPressedLastSync)
         {
@@ -411,7 +435,7 @@ namespace OpenXRVk
 
     AZ::RHI::ResultCode Input::GetVisualizedSpacePose(OpenXRVk::SpaceType visualizedSpaceType, AZ::RPI::PoseData& outPoseData) const
     {
-        const auto spaceIndex = static_cast<uint32_t>(visualizedSpaceType);
+        const auto spaceIndex = static_cast<AZ::u32>(visualizedSpaceType);
         if (spaceIndex < AZStd::size(m_xrVisualizedSpaceLocations))
         {
             const XrQuaternionf& orientation = m_xrVisualizedSpaceLocations[spaceIndex].pose.orientation;
@@ -455,8 +479,7 @@ namespace OpenXRVk
     bool Input::GetButtonState(const AzFramework::InputChannelId& channelId) const
     {
         const auto& state = m_xrControllerImpl->GetRawState();
-        const AZ::u32 mask = state.m_buttonIdsToBitMasks.at(channelId);
-        return (state.m_digitalButtonStates & mask) != 0;
+        return state.GetDigitalButtonState(channelId);
     }
 
     bool Input::GetXButtonState() const
@@ -510,4 +533,5 @@ namespace OpenXRVk
             ? state.m_leftTriggerState
             : state.m_rightTriggerState;
     }
-}
+
+} // namespace OpenXRVk

+ 1 - 1
Gems/OpenXRVk/Code/Source/OpenXRVkInstance.cpp

@@ -10,7 +10,7 @@
 #include <OpenXRVk/OpenXRVkUtils.h>
 #include <Atom/RHI.Reflect/Vulkan/XRVkDescriptors.h>
 #include <AzCore/Casting/numeric_cast.h>
-#include <OpenXRVk_Traits_Platform.h>
+#include <OpenXRVkCommon.h>
 
 namespace OpenXRVk
 {

+ 3 - 1
Gems/OpenXRVk/Code/Source/OpenXRVkModule.cpp

@@ -9,6 +9,7 @@
 #include <AzCore/Memory/SystemAllocator.h>
 #include <AzCore/Module/Module.h>
 #include <OpenXRVk/OpenXRVkSystemComponent.h>
+#include <XRCameraMovementComponent.h>
 
 namespace OpenXRVk
 {   
@@ -24,7 +25,8 @@ namespace OpenXRVk
             : AZ::Module()
         {
             m_descriptors.insert(m_descriptors.end(), {
-                    SystemComponent::CreateDescriptor(),
+                SystemComponent::CreateDescriptor(),
+                XRCameraMovementComponent::CreateDescriptor(),
             });
         }
 

+ 5 - 1
Gems/OpenXRVk/Code/Source/OpenXRVkSystemComponent.cpp

@@ -6,7 +6,9 @@
  *
  */
 
+
 #include <AzCore/Serialization/SerializeContext.h>
+
 #include <OpenXRVk/OpenXRVkDevice.h>
 #include <OpenXRVk/OpenXRVkInput.h>
 #include <OpenXRVk/OpenXRVkInstance.h>
@@ -24,11 +26,13 @@ namespace OpenXRVk
 
     void SystemComponent::Reflect(AZ::ReflectContext* context)
     {
-        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
         {
             serializeContext->Class<SystemComponent, AZ::Component>()
                 ->Version(1);
         }
+
+        AzFramework::InputDeviceXRController::Reflect(context);
     }
 
     SystemComponent::SystemComponent()

+ 5 - 5
Gems/OpenXRVk/Code/Source/Platform/Android/OpenXRVk_Traits_Android.cpp → Gems/OpenXRVk/Code/Source/Platform/Android/OpenXRVkCommon_Android.cpp

@@ -6,10 +6,10 @@
  *
  */
 
+#include <OpenXRVkCommon.h>
 #include <AzCore/Android/AndroidEnv.h>
-
 #include <OpenXRVk/OpenXRVkUtils.h>
-#include <OpenXRVk_Traits_Android.h>
+
 
 namespace OpenXRVk::Platform
 {
@@ -47,11 +47,11 @@ namespace OpenXRVk::Platform
 
         return true;
     }
-    
+
     void OpenXRBeginFrameInternal()
     {
     }
-    
+
     void OpenXREndFrameInternal()
     {
         // OpenXR's xrEndFrame function internally uses the application's Java VM (passed in OpenXRInitializeLoader).
@@ -62,7 +62,7 @@ namespace OpenXRVk::Platform
         AZ_Assert(androidEnv != nullptr, "Invalid android environment");
         androidEnv->GetJniEnv();
     }
-    
+
     void OpenXRPostFrameInternal()
     {
         // Now that EndFrame has finished, calling GetJniEnv() again from the main thread

+ 0 - 18
Gems/OpenXRVk/Code/Source/Platform/Android/OpenXRVk_Traits_Android.h

@@ -8,21 +8,3 @@
 #pragma once
 
 #define O3DE_TRAIT_DISABLE_FAILED_OPENXRVK_TESTS
-
-namespace OpenXRVk::Platform
-{
-    //! Initializes the XR loader for this platform.
-    bool OpenXRInitializeLoader();
-
-    //! Called when the device is beginning a frame for processing.
-    //! @note This function is called from the thread related to the presentation queue.
-    void OpenXRBeginFrameInternal();
-    
-    //! Called when the device is ending a frame for processing. 
-    //! @note This function is called from the thread related to the presentation queue.
-    void OpenXREndFrameInternal();
-    
-    //! Called after the EndFrame has been executed.
-    //! @note This function is called from the main thread.
-    void OpenXRPostFrameInternal();
-}

+ 2 - 1
Gems/OpenXRVk/Code/Source/Platform/Android/platform_private_android_files.cmake

@@ -7,9 +7,10 @@
 #
 
 set(FILES
+    OpenXRVkCommon_Android.cpp
     OpenXRVk_Traits_Android.h
-    OpenXRVk_Traits_Android.cpp
     OpenXRVk_Traits_Platform.h
+    ../Common/Default/InputDeviceXRController_Default.cpp
     ../Common/Default/OculusTouch_Default.cpp
     ../Common/Default/OculusTouch_Default.h
 )

+ 23 - 0
Gems/OpenXRVk/Code/Source/Platform/Common/Default/InputDeviceXRController_Default.cpp

@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <OpenXRVk/InputDeviceXRController.h>
+#include "OculusTouch_Default.h"
+
+namespace AzFramework
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceXRController::Implementation* InputDeviceXRController::Implementation::Create(
+        InputDeviceXRController& inputDevice)
+    {
+        // Future versions of this function may be able to select from a variety of different device
+        // types and do so based on knowledge of the hardware, but for now force the Oculus Touch controller.
+        return aznew OpenXRVk::InputDeviceOculusTouch(inputDevice);
+    }
+
+} // namespace AzFramework

+ 0 - 8
Gems/OpenXRVk/Code/Source/Platform/Common/Default/OculusTouch_Default.h

@@ -60,11 +60,3 @@ namespace OpenXRVk
     };
 
 } // namespace OpenXRVk
-
-////////////////////////////////////////////////////////////////////////////////////////////////
-// static
-inline AzFramework::InputDeviceXRController::Implementation* AzFramework::InputDeviceXRController::Implementation::Create(
-    InputDeviceXRController& inputDevice)
-{
-    return aznew OpenXRVk::InputDeviceOculusTouch(inputDevice);
-}

+ 20 - 0
Gems/OpenXRVk/Code/Source/Platform/Common/Unimplemented/InputDeviceXRController_Unimplemented.cpp

@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <OpenXRVk/InputDeviceXRController.h>
+
+namespace AzFramework
+{
+    ////////////////////////////////////////////////////////////////////////////////////////////////
+    InputDeviceXRController::Implementation* InputDeviceXRController::Implementation::Create(
+        InputDeviceXRController& inputDevice)
+    {
+        return nullptr;
+    }
+
+} // namespace AzFramework

+ 4 - 4
Gems/OpenXRVk/Code/Source/Platform/Linux/OpenXRVk_Traits_Linux.cpp → Gems/OpenXRVk/Code/Source/Platform/Common/Unimplemented/OpenXRVkCommon_Unimplemented.cpp

@@ -6,7 +6,7 @@
  *
  */
 
-#include <OpenXRVk_Traits_Linux.h>
+#include <OpenXRVkCommon.h>
 
 namespace OpenXRVk::Platform
 {
@@ -14,15 +14,15 @@ namespace OpenXRVk::Platform
     {
         return true;
     }
-    
+
     void OpenXRBeginFrameInternal()
     {
     }
-    
+
     void OpenXREndFrameInternal()
     {
     }
-    
+
     void OpenXRPostFrameInternal()
     {
     }

+ 0 - 18
Gems/OpenXRVk/Code/Source/Platform/Linux/OpenXRVk_Traits_Linux.h

@@ -8,21 +8,3 @@
 #pragma once
 
 #define O3DE_TRAIT_DISABLE_FAILED_OPENXRVK_TESTS
-
-namespace OpenXRVk::Platform
-{
-    //! Initializes the XR loader for this platform.
-    bool OpenXRInitializeLoader();
-
-    //! Called when the device is beginning a frame for processing.
-    //! @note This function is called from the thread related to the presentation queue.
-    void OpenXRBeginFrameInternal();
-    
-    //! Called when the device is ending a frame for processing. 
-    //! @note This function is called from the thread related to the presentation queue.
-    void OpenXREndFrameInternal();
-    
-    //! Called after the EndFrame has been executed.
-    //! @note This function is called from the main thread.
-    void OpenXRPostFrameInternal();
-}

+ 2 - 1
Gems/OpenXRVk/Code/Source/Platform/Linux/platform_private_linux_files.cmake

@@ -8,8 +8,9 @@
 
 set(FILES
     OpenXRVk_Traits_Linux.h
-    OpenXRVk_Traits_Linux.cpp
     OpenXRVk_Traits_Platform.h
+    ../Common/Default/InputDeviceXRController_Default.cpp
     ../Common/Default/OculusTouch_Default.cpp
     ../Common/Default/OculusTouch_Default.h
+    ../Common/Unimplemented/OpenXRVkCommon_Unimplemented.cpp
 )

+ 1 - 1
Gems/OpenXRVk/Code/Source/Platform/Mac/OpenXRVk_Traits_Mac.h

@@ -7,4 +7,4 @@
  */
 #pragma once
 
-#define O3DE_TRAIT_DISABLE_FAILED_OPENXRVK_TESTS
+#define O3DE_TRAIT_DISABLE_FAILED_OPENXRVK_TESTS

+ 2 - 0
Gems/OpenXRVk/Code/Source/Platform/Mac/platform_private_mac_files.cmake

@@ -9,4 +9,6 @@
 set(FILES
     OpenXRVk_Traits_Mac.h
     OpenXRVk_Traits_Platform.h
+    ../Common/Unimplemented/InputDeviceXRController_Unimplemented.cpp
+    ../Common/Unimplemented/OpenXRVkCommon_Unimplemented.cpp
 )

+ 0 - 29
Gems/OpenXRVk/Code/Source/Platform/Windows/OpenXRVk_Traits_Windows.cpp

@@ -1,29 +0,0 @@
-/*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
-
-#include <OpenXRVk_Traits_Windows.h>
-
-namespace OpenXRVk::Platform
-{
-    bool OpenXRInitializeLoader()
-    {
-        return true;
-    }
-    
-    void OpenXRBeginFrameInternal()
-    {
-    }
-    
-    void OpenXREndFrameInternal()
-    {
-    }
-    
-    void OpenXRPostFrameInternal()
-    {
-    }
-}

+ 0 - 18
Gems/OpenXRVk/Code/Source/Platform/Windows/OpenXRVk_Traits_Windows.h

@@ -8,21 +8,3 @@
 #pragma once
 
 #define O3DE_TRAIT_DISABLE_FAILED_OPENXRVK_TESTS
-
-namespace OpenXRVk::Platform
-{
-    //! Initializes the XR loader for this platform.
-    bool OpenXRInitializeLoader();
-
-    //! Called when the device is beginning a frame for processing.
-    //! @note This function is called from the thread related to the presentation queue.
-    void OpenXRBeginFrameInternal();
-    
-    //! Called when the device is ending a frame for processing. 
-    //! @note This function is called from the thread related to the presentation queue.
-    void OpenXREndFrameInternal();
-    
-    //! Called after the EndFrame has been executed.
-    //! @note This function is called from the main thread.
-    void OpenXRPostFrameInternal();
-}

+ 2 - 1
Gems/OpenXRVk/Code/Source/Platform/Windows/platform_private_windows_files.cmake

@@ -8,8 +8,9 @@
 
 set(FILES
     OpenXRVk_Traits_Windows.h
-    OpenXRVk_Traits_Windows.cpp
     OpenXRVk_Traits_Platform.h
+    ../Common/Default/InputDeviceXRController_Default.cpp
     ../Common/Default/OculusTouch_Default.cpp
     ../Common/Default/OculusTouch_Default.h
+    ../Common/Unimplemented/OpenXRVkCommon_Unimplemented.cpp
 )

+ 1 - 1
Gems/OpenXRVk/Code/Source/Platform/iOS/OpenXRVk_Traits_iOS.h

@@ -7,4 +7,4 @@
  */
 #pragma once
 
-#define O3DE_TRAIT_DISABLE_FAILED_OPENXRVK_TESTS
+#define O3DE_TRAIT_DISABLE_FAILED_OPENXRVK_TESTS

+ 2 - 0
Gems/OpenXRVk/Code/Source/Platform/iOS/platform_private_ios_files.cmake

@@ -9,4 +9,6 @@
 set(FILES
     OpenXRVk_Traits_iOS.h
     OpenXRVk_Traits_Platform.h
+    ../Common/Unimplemented/InputDeviceXRController_Unimplemented.cpp
+    ../Common/Unimplemented/OpenXRVkCommon_Unimplemented.cpp
 )

+ 166 - 0
Gems/OpenXRVk/Code/Source/XRCameraMovementComponent.cpp

@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <XRCameraMovementComponent.h>
+
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/RTTI/BehaviorContext.h>
+
+#include <OpenXRVk/InputDeviceXRController.h>
+
+#include <Atom/RPI.Public/ViewProviderBus.h>
+#include <Atom/RPI.Public/View.h>
+#include <AzFramework/Components/CameraBus.h>
+
+#include <Atom/RPI.Public/ViewportContext.h>
+#include <Atom/RPI.Public/ViewportContextBus.h>
+
+
+namespace OpenXRVk
+{
+    static AZ::Transform GetCameraTransformFromCurrentView()
+    {
+        if (const auto viewportContextMgr = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
+            viewportContextMgr != nullptr)
+        {
+            if (const AZ::RPI::ViewportContextPtr viewportContext = viewportContextMgr->GetDefaultViewportContext();
+                viewportContext != nullptr)
+            {
+                if (const AZ::RPI::ViewPtr view = viewportContext->GetDefaultView();
+                    view != nullptr)
+                {
+                    return view->GetCameraTransform();
+                }
+            }
+        }
+        return AZ::Transform::CreateIdentity();
+    }
+
+    void XRCameraMovementComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<XRCameraMovementComponent, AZ::Component>()
+                ->Version(1)
+                ->Field("Move Speed", &XRCameraMovementComponent::m_moveSpeed)
+                ->Field("Movement Sensitivity", &XRCameraMovementComponent::m_movementSensitivity)
+                ;
+
+            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+            {
+                editContext->Class<XRCameraMovementComponent>("XRCameraMovementComponent", "[Description of functionality provided by this component]")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "ComponentCategory")
+                    ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/Component_Placeholder.svg")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &XRCameraMovementComponent::m_moveSpeed, "Move Speed", "Speed of camera movement")
+                    ->Attribute(AZ::Edit::Attributes::Min, 1.f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 100.f)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &XRCameraMovementComponent::m_movementSensitivity, "Move Sensitivity", "Fine movement sensitivity factor")
+                    ->Attribute(AZ::Edit::Attributes::Min, 0.f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 1.f)
+                    ;
+            }
+        }
+
+        if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
+        {
+            behaviorContext->Class<XRCameraMovementComponent>("XRCameraMovement Component Group")
+                ->Attribute(AZ::Script::Attributes::Category, "OpenXRVk Gem Group")
+                ;
+        }
+    }
+
+    void XRCameraMovementComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(AZ_CRC_CE("CameraMovementService"));
+    }
+
+    void XRCameraMovementComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(AZ_CRC_CE("CameraMovementService"));
+    }
+
+    void XRCameraMovementComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+        required.push_back(AZ_CRC_CE("TransformService"));
+    }
+
+    void XRCameraMovementComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+    }
+
+    void XRCameraMovementComponent::Activate()
+    {
+        AzFramework::InputChannelEventListener::Connect();
+        AZ::TickBus::Handler::BusConnect();
+    }
+
+    void XRCameraMovementComponent::Deactivate()
+    {
+        AZ::TickBus::Handler::BusDisconnect();
+        AzFramework::InputChannelEventListener::Disconnect();
+    }
+
+    void XRCameraMovementComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint timePoint)
+    {
+        AZ::Transform cameraTransform = GetCameraTransformFromCurrentView();
+
+        // Update movement...
+        const float moveSpeed = m_moveSpeed * deltaTime;
+        const AZ::Vector3 movementVec = (cameraTransform.GetBasisX() * m_movement.GetX())
+            + (cameraTransform.GetBasisY() * m_movement.GetY())
+            + (AZ::Vector3{0.f, 0.f, 1.f} * m_movement.GetZ()); // use a fixed UP for the Z direction
+        const AZ::Vector3 newPosition{ (cameraTransform.GetTranslation() + (movementVec * moveSpeed)) };
+        cameraTransform.SetTranslation(newPosition);
+
+        AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTM, cameraTransform);
+    }
+
+    bool XRCameraMovementComponent::OnInputChannelEventFiltered([[maybe_unused]] const AzFramework::InputChannel& inputChannel)
+    {
+        const auto& deviceId = inputChannel.GetInputDevice().GetInputDeviceId();
+        if (AzFramework::InputDeviceXRController::IsXRControllerDevice(deviceId))
+        {
+            OnXRControllerEvent(inputChannel);
+        }
+        return false;
+    }
+
+    void XRCameraMovementComponent::OnXRControllerEvent([[maybe_unused]] const AzFramework::InputChannel& inputChannel)
+    {
+        const auto& channelId = inputChannel.GetInputChannelId();
+
+        // This currently uses specific xr controller channels to drive the movement.  Future iterations might
+        // use a higher-level concepts like InputMappings and InputContexts to generalize to additional
+        // input devices.
+
+        // Left thumb-stick X/Y move the camera
+        if (channelId == AzFramework::InputDeviceXRController::ThumbStickAxis1D::LX)
+        {
+            m_movement.SetX(inputChannel.GetValue() * m_movementSensitivity);
+        }
+        if (channelId == AzFramework::InputDeviceXRController::ThumbStickAxis1D::LY)
+        {
+            m_movement.SetY(inputChannel.GetValue() * m_movementSensitivity);
+        }
+
+        // A/B buttons update the height in Z of the camera
+        if (channelId == AzFramework::InputDeviceXRController::Button::A)
+        {   // down
+            m_movement.SetZ(-inputChannel.GetValue() * m_movementSensitivity);
+        }
+        if (channelId == AzFramework::InputDeviceXRController::Button::B)
+        {   // up
+            m_movement.SetZ(inputChannel.GetValue() * m_movementSensitivity);
+        }
+    }
+
+} // namespace OpenXRVk

+ 59 - 0
Gems/OpenXRVk/Code/Source/XRCameraMovementComponent.h

@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+#include <AzFramework/Input/Events/InputChannelEventListener.h>
+
+
+namespace OpenXRVk
+{
+    //! XRCameraMovementComponent integrates XR Controller inputs to control a camera.
+    //! This is an example that hooks up a limited set of inputs, mostly thumbsticks, to
+    //! drive the camera position to new places.
+    class XRCameraMovementComponent
+        : public AZ::Component
+        , public AZ::TickBus::Handler
+        , public AzFramework::InputChannelEventListener
+    {
+    public:
+        AZ_COMPONENT(OpenXRVk::XRCameraMovementComponent, "{7FEC0A04-D994-445C-B8DE-190D03BC3820}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+    protected:
+        // AZ::Component
+        void Activate() override;
+        void Deactivate() override;
+
+        // AZ::TickBus::Handler
+        void OnTick(float deltaTime, AZ::ScriptTimePoint timePoint) override;
+
+        // AzFramework::InputChannelEventListener
+        bool OnInputChannelEventFiltered(const AzFramework::InputChannel& inputChannel) override;
+
+    private:
+        void OnXRControllerEvent(const AzFramework::InputChannel& inputChannel);
+
+        // Transient data...
+        AZ::Vector3 m_movement = AZ::Vector3::CreateZero();
+        float m_heightZ = 0.f;
+
+        // Serialized data...
+        float m_moveSpeed = 20.f;
+        float m_movementSensitivity = 0.025f;
+    };
+
+} // namespace OpenXRVk

+ 3 - 0
Gems/OpenXRVk/Code/openxrvk_private_common_files.cmake

@@ -18,6 +18,7 @@ set(FILES
     Include/OpenXRVk/OpenXRVkSystemComponent.h
     Include/OpenXRVk/OpenXRVkUtils.h
     Source/InputDeviceXRController.cpp
+    Source/OpenXRVkCommon.h
     Source/OpenXRVkDevice.cpp
     Source/OpenXRVkInput.cpp
     Source/OpenXRVkInstance.cpp
@@ -27,4 +28,6 @@ set(FILES
     Source/OpenXRVkSwapChain.cpp
     Source/OpenXRVkSystemComponent.cpp
     Source/OpenXRVkUtils.cpp
+    Source/XRCameraMovementComponent.cpp
+    Source/XRCameraMovementComponent.h
 )

+ 1 - 1
Projects/OpenXRTest/Gem/Source/OpenXRTestSystemComponent.h

@@ -22,7 +22,7 @@ namespace OpenXRTest
         static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
 
         OpenXRTestSystemComponent();
-        ~OpenXRTestSystemComponent();
+        ~OpenXRTestSystemComponent() override;
 
     protected:
         ////////////////////////////////////////////////////////////////////////

+ 21 - 12
Projects/OpenXRTest/Levels/XR_Office/XR_Office.prefab

@@ -165,7 +165,7 @@
                     "Controller": {
                         "Configuration": {
                             "Field of View": 80.0,
-                            "EditorEntityId": 6533622865231202314
+                            "EditorEntityId": 16589391527782614938
                         }
                     }
                 },
@@ -193,14 +193,14 @@
                     "Parent Entity": "Entity_[1176639161715]",
                     "Transform Data": {
                         "Translate": [
-                            -2.2044129371643066,
-                            -1.8803668022155762,
-                            2.2697277069091797
+                            -2.2034757137298584,
+                            -1.8803786039352417,
+                            2.269624948501587
                         ],
                         "Rotate": [
-                            -18.422677993774414,
-                            10.049921989440918,
-                            -27.649707794189453
+                            -18.42388153076172,
+                            10.038938522338867,
+                            -27.622568130493164
                         ]
                     }
                 },
@@ -210,7 +210,16 @@
                 },
                 "Component_[5265045084611556958]": {
                     "$type": "EditorDisabledCompositionComponent",
-                    "Id": 5265045084611556958
+                    "Id": 5265045084611556958,
+                    "DisabledComponents": [
+                        {
+                            "$type": "GenericComponentWrapper",
+                            "Id": 7255796294953281766,
+                            "m_template": {
+                                "$type": "FlyCameraInputComponent"
+                            }
+                        }
+                    ]
                 },
                 "Component_[7087500764311260607]": {
                     "$type": "AZ::Render::EditorSsaoComponent",
@@ -220,11 +229,11 @@
                     "$type": "EditorPendingCompositionComponent",
                     "Id": 7169798125182238623
                 },
-                "Component_[7255796294953281766]": {
+                "Component_[8829265314591669444]": {
                     "$type": "GenericComponentWrapper",
-                    "Id": 7255796294953281766,
+                    "Id": 8829265314591669444,
                     "m_template": {
-                        "$type": "FlyCameraInputComponent"
+                        "$type": "OpenXRVk::XRCameraMovementComponent"
                     }
                 },
                 "Component_[8866210352157164042]": {
@@ -2029,7 +2038,7 @@
                                 },
                                 "assetHint": "assets/textures/reflectionprobes/refprobmain__5671e21b-ba4c-48c7-8e30-8f8a0428ab82__iblspecularcm256.dds.streamingimage"
                             },
-                            "EntityId": 12192310272083875772,
+                            "EntityId": 6969507080663320545,
                             "ShowVisualization": false,
                             "RenderExposure": 1.0
                         }