Thumbstick Movement & Rotation

The project a is stage reference space that allows the player to walk around, but I need a way to move with the thumbsticks as well. I have an created an extra XrPosef called m_Local that is the amount the player as moved and rotated with the thumbsticks. To get my position I use m_HMD + m_Local and for rotation use a QuaternionMultiply function on m_HMD and m_Local.

My movement code works, but is this the right way to move with the thumbstick in OpenXR?

      void movelocalz(float Time, XrTime predictedDisplayTime)
        {
            // Move The Direction The Player Is Facing
            if (m_Move.z != 0.0f)
            {
                float Distance = m_Move.z;
                m_Move.z = 0.0f;

                XrSpaceLocation viewSpaceLocation = {XR_TYPE_SPACE_LOCATION};
                XrResult result;

                // Locate the view space relative to the world space at the predicted display time
                result = xrLocateSpace(m_viewSpace, m_worldSpace, predictedDisplayTime, &viewSpaceLocation);
                
                if (XR_SUCCEEDED(result) &&
                    (viewSpaceLocation.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) &&
                    (viewSpaceLocation.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT))
                {
                    // Calculate forward vector based on the orientation
                    XrQuaternionf RightHandedOrientation = viewSpaceLocation.pose.orientation;
                    XrQuaternionf LeftHandedOrientation;

                    // Convert right-handed to left-handed coordinate system
                    RightToLeftCoordinateSystem(RightHandedOrientation.w, RightHandedOrientation.x, RightHandedOrientation.y, RightHandedOrientation.z,
                                                LeftHandedOrientation.w, LeftHandedOrientation.x, LeftHandedOrientation.y, LeftHandedOrientation.z);

                    // Define the initial forward vector in a left-handed coordinate system
                    // Assuming forward is along +Z axis
                    float initialForwardX = 0.0f, initialForwardY = 0.0f, initialForwardZ = 1.0f;
                    XrVector3f forwardDirection;

                    // Rotate initial forward vector by the quaternion
                    RotateVectorByQuaternion(LeftHandedOrientation.w, LeftHandedOrientation.x, LeftHandedOrientation.y, LeftHandedOrientation.z, 
                                            initialForwardX, initialForwardY, initialForwardZ,
                                            forwardDirection.x, forwardDirection.y, forwardDirection.z);

                    // Calculate speed (distance over time)
                    float speed = Distance / Time;

                    // Update position based on speed and forward direction
                    m_Local.position.x += forwardDirection.x * speed;
                  if (m_FollowHMDPitch)  m_Local.position.y += forwardDirection.y * speed;
                    m_Local.position.z += forwardDirection.z * speed;
                }
            }
        }

Now as for rotation, I cannot get this to work correctly. The HMD tilt is off when I start rotating the m_Local with the thumbsticks.


        void rotatelocaly(float Time, XrTime predictedDisplayTime)
        {
            if (m_Rotate.y != 0.0f)
            {
                float angleRadians = m_Rotate.y; // Assuming m_Rotate.y is in radians as noted.
                m_Rotate.y = 0.0f; // Reset the rotation amount after applying it.

                // Components of the quaternion for a rotation around the Y-axis
                float qw = cos(angleRadians / 2.0f);
                float qx = 0.0f;
                float qy = sin(angleRadians / 2.0f);
                float qz = 0.0f;

                // Assuming m_Local.orientation represents the current orientation as a quaternion
                XrQuaternionf currentOrientation = m_Local.orientation;
                XrQuaternionf newOrientation;

                // Quaternion multiplication (current orientation * rotation quaternion)
                newOrientation.w = currentOrientation.w * qw - currentOrientation.y * qy;
                newOrientation.x = currentOrientation.x * qw + currentOrientation.z * qy;
                newOrientation.y = currentOrientation.y * qw + currentOrientation.w * qy;
                newOrientation.z = currentOrientation.z * qw - currentOrientation.x * qy;

                // Update the local orientation with the new orientation
                m_Local.orientation = newOrientation;
            }
        }

I also read to destroy and recreate the stage reference space to update the pose. I could get rid of m_Local completely if I done this. Not sure which is the correct path.

I ended up recreating the reference space everytime when I rotate and move forward with the thumbstick.

A lot of math here. It seems to be working perfectly now. Some code incase someone else gets stuck with this:

        XrPosef      m_Local  = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}}; // Use This to build/rebuild your reference space
        XrVector3f   m_Move   = {0.0f, 0.0f, 0.0f}; // Amount to move, update when thumbstick is moved.
        XrVector3f   m_Rotate = {0.0f, 0.0f, 0.0f}; // Amount to rotate, update when thumbstick is moved.
		
		void NormalizeQuaternion(float& qw, float& qx, float& qy, float& qz)
		{
			float norm = sqrt(qw * qw + qx * qx + qy * qy + qz * qz);
			if (norm == 0)
			{
				qw = 1; qx = 0; qy = 0; qz = 0;  // Default to identity quaternion to avoid division by zero
			}
			else
			{
				qw /= norm;
				qx /= norm;
				qy /= norm;
				qz /= norm;
			}
		}
		
		void RotateVectorByQuaternion(float qw, float qx, float qy, float qz, 
									  float vx, float vy, float vz,
									  float &outX, float &outY, float &outZ)
		{
			// Normalize the quaternion to ensure it is a unit quaternion
			NormalizeQuaternion(qw, qx, qy, qz);

			// Quaternion-vector multiplication
			float uvx = qy*vz - qz*vy;
			float uvy = qz*vx - qx*vz;
			float uvz = qx*vy - qy*vx;

			float uuvx = qy*uvz - qz*uvy;
			float uuvy = qz*uvx - qx*uvz;
			float uuvz = qx*uvy - qy*uvx;

			float w2v = qw * 2.0f * (vx*qx + vy*qy + vz*qz);

			outX = vx + uvx * 2.0f * qw + uuvx * 2.0f;
			outY = vy + uvy * 2.0f * qw + uuvy * 2.0f;
			outZ = vz + uvz * 2.0f * qw + uuvz * 2.0f;
		}		
		
		void RotatePointXYZ(double x, double y, double z, double angleDegreesX, double angleDegreesY, double angleDegreesZ,
                    double& xPrime, double& yPrime, double& zPrime)
		{
			double radX = DegreesToRadians(angleDegreesX);
			double radY = DegreesToRadians(angleDegreesY);
			double radZ = DegreesToRadians(angleDegreesZ);

			// Rotation around X-axis
			double y1 = y * cos(radX) - z * sin(radX);
			double z1 = y * sin(radX) + z * cos(radX);
			double x1 = x;

			// Rotation around Y-axis
			double z2 = z1 * cos(radY) - x1 * sin(radY);
			double x2 = z1 * sin(radY) + x1 * cos(radY);
			double y2 = y1;

			// Rotation around Z-axis
			xPrime = x2 * cos(radZ) - y2 * sin(radZ);
			yPrime = x2 * sin(radZ) + y2 * cos(radZ);
			zPrime = z2;
		}
		
		void RebuildReferenceSpace()
        {
            // Destroy the old reference space
            xrDestroySpace(m_worldSpace);
            
            // Create Choosen World Reference Space
            XrReferenceSpaceCreateInfo worldSpaceCreateInfo = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
            worldSpaceCreateInfo.referenceSpaceType = eWorldType;
            worldSpaceCreateInfo.poseInReferenceSpace = m_Local;
            XrResult result = xrCreateReferenceSpace(m_session, &worldSpaceCreateInfo, &m_worldSpace);
            
            if (result != XR_SUCCESS)
            {
                setopenxrstatus(Failed_Status);
                XR_MESSAGE("Failed to create Stage ReferenceSpace.");
                return;
            }
        }
		void movelocalz(float Time, XrTime predictedDisplayTime) // Move with your thumbstick
        {
            if (m_Move.z != 0.0f)
            {
                float Distance = m_Move.z;
                m_Move.z = 0.0f;

                // Locate the views from the view configuration within the (reference) space at the display time.
                std::vector<XrView> views(m_viewConfigurationViews.size(), {XR_TYPE_VIEW});

                XrViewState viewState{XR_TYPE_VIEW_STATE};  // Will contain information on whether the position and/or orientation is valid and/or tracked.
                XrViewLocateInfo viewLocateInfo{XR_TYPE_VIEW_LOCATE_INFO};
                viewLocateInfo.viewConfigurationType = m_viewConfiguration;
                viewLocateInfo.displayTime = predictedDisplayTime;
                viewLocateInfo.space = m_worldSpace;
                uint32_t viewCount = 0;
                XrResult result = xrLocateViews(m_session, &viewLocateInfo, &viewState, static_cast<uint32_t>(views.size()), &viewCount, views.data());

                if (result == XR_SUCCESS)
                {
                    XrQuaternionf viewOrientation  = views[0].pose.orientation;
                    XrQuaternionf localOrientation = m_Local.orientation;
                    XrQuaternionf finishOrientation;

                    QuaternionMultiply(viewOrientation.w, viewOrientation.x, viewOrientation.y, viewOrientation.z,
                                       localOrientation.w, localOrientation.x, localOrientation.y, localOrientation.z, true,
                                       finishOrientation.w,  finishOrientation.x,  finishOrientation.y,  finishOrientation.z);

                    float moveX = 0.0f, moveY = 0.0f, moveZ = 1.0f;
                    XrVector3f movement;

                    RotateVectorByQuaternion(finishOrientation.w, finishOrientation.x, finishOrientation.y, finishOrientation.z, 
                                            moveX, moveY, moveZ,
                                            movement.x, movement.y, movement.z);

                    float speed = Distance / Time;

                    m_Local.position.x += movement.x * speed;
                    if (m_FollowHMDPitch) m_Local.position.y += movement.y * speed;
                    m_Local.position.z += movement.z * speed;

                    RebuildReferenceSpace();
                }
            }
        }
		void rotatelocaly(float Time, XrTime predictedDisplayTime) // Rotate with your thumbstick
        {
            if (m_Rotate.y != 0.0f)
            {
                float amountX = 0;
                float amountY = m_Rotate.y; 
                float amountZ = 0;

                m_Rotate.y = 0.0f;

                double newX, newY, newZ;

                RotatePointXYZ(m_Local.position.x, m_Local.position.y, m_Local.position.z,
                              amountX, amountY, amountZ,
                              newX, newY, newZ);

                m_Local.position.x = newX;
                m_Local.position.y = newY;
                m_Local.position.z = newZ;

				float QuatW, QuatX, QuatY ,QuatZ;
				converteulerdegreestoquaternion(amountX, amountY, amountZ, QuatW, QuatX, QuatY, QuatZ)
            
                XrQuaternionf rotationQuaternion;
                rotationQuaternion.w = QuatW;
                rotationQuaternion.x = QuatX;
                rotationQuaternion.y = QuatY;
                rotationQuaternion.z = QuatZ;

                XrQuaternionf currentOrientation = m_Local.orientation;
                XrQuaternionf finishOrientation;

                QuaternionMultiply(currentOrientation.w, currentOrientation.x, currentOrientation.y, currentOrientation.z,
                                   rotationQuaternion.w, rotationQuaternion.x, rotationQuaternion.y, rotationQuaternion.z, true,
                                    finishOrientation.w,  finishOrientation.x,  finishOrientation.y,  finishOrientation.z);
                    
                m_Local.orientation = finishOrientation;

                RebuildReferenceSpace();
            }
        }

Just as an FYI, moving with the thumbstick is usually a quick way to motion sickness. There is a reason so many VR games use teleport instead. Discontinuous “jumps” like that are much more comfortable than “sliding”