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”

Trying to port an older VR project to OpenXR, so needed this type of movement.