Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

How to Get Raw (Positional) Data from HTC Vive?

4.60/5 (4 votes)
21 Aug 2017CPOL2 min read 39.6K  
This article explains how to get position (x,y,z) and rotation (qw, qx, qy, qz) of HTC Vive headset or controllers.

Introduction

If you want to read the position (x,y,z) and rotation (qw, qx, qy, qz) of HTC Vive headset or controllers, then this article is for you. In this article, we create a background Windows application that grabs the positional data and write them into a text file and on the console at the same time using powershell. The final project is available here at github.

Step-by-step Explanation

In order to get raw positional (and rotational) data out of HTC Vive system, do this:

1) Get this project (which is provided by Valve Software): https://github.com/ValveSoftware/openvr

2) Open samples/samples_vs2010.sln solution file by visual studio. Then, run it to make sure everyting works. You should see a window like the following appears and you can turn your head around and see the cubes. If you turn on the controllers, you should also be able to see them.

Image 1

Note: If you get the error "Unable to init VR runtime: Shared IPC Connect Failed After Multiple Attempts (308)", you should run your visual studio as administrator (by right-clicking on its icon and choosing 'Run as administrator).

3) Make the application as a background application. To do this, in "hellovr_opengl_main.cpp" file, find vr::VR_Init() call and change this:

C++
m_pHMD = vr::VR_Init( &eError, vr::VRApplication_Scene );

to this:

C++
m_pHMD = vr::VR_Init(&eError, vr::VRApplication_Background);

This has two effects:

  • It makes the application to run in the background so the window will not be displayed.
  • It does not start SteamVR anymore. So for running your background application, you should make sure SteamVR is already running.

4) Take out all the stuff related to openGL and scene creation.

In the CMainApplication::BInit() method, comment the following (I'm displaying it as commented):

C++
    //m_pRenderModels = (vr::IVRRenderModels *)vr::VR_GetGenericInterface( vr::IVRRenderModels_Version, &eError );
    //if( !m_pRenderModels )
    //{
    //    m_pHMD = NULL;
    //    vr::VR_Shutdown();

    //    char buf[1024];
    //    sprintf_s( buf, sizeof( buf ), "Unable to get render model interface: %s", vr::VR_GetVRInitErrorAsEnglishDescription( eError ) );
    //    SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "VR_Init Failed", buf, NULL );
    //    return false;
    //}

    //int nWindowPosX = 700;
    //int nWindowPosY = 100;
    //Uint32 unWindowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;

    //SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 4 );
    //SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 1 );
    //SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY );
    //SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );

    //SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 0 );
    //SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, 0 );
    //if( m_bDebugOpenGL )
    //    SDL_GL_SetAttribute( SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG );

    //m_pCompanionWindow = SDL_CreateWindow( "hellovr", nWindowPosX, nWindowPosY, m_nCompanionWindowWidth, m_nCompanionWindowHeight, unWindowFlags );
    //if (m_pCompanionWindow == NULL)
    //{
    //    printf( "%s - Window could not be created! SDL Error: %s\n", __FUNCTION__, SDL_GetError() );
    //    return false;
    //}

    //m_pContext = SDL_GL_CreateContext(m_pCompanionWindow);
    //if (m_pContext == NULL)
    //{
    //    printf( "%s - OpenGL context could not be created! SDL Error: %s\n", __FUNCTION__, SDL_GetError() );
    //    return false;
    //}

    //glewExperimental = GL_TRUE;
    //GLenum nGlewError = glewInit();
    //if (nGlewError != GLEW_OK)
    //{
    //    printf( "%s - Error initializing GLEW! %s\n", __FUNCTION__, glewGetErrorString( nGlewError ) );
    //    return false;
    //}
    //glGetError(); // to clear the error caused deep in GLEW

    //if ( SDL_GL_SetSwapInterval( m_bVblank ? 1 : 0 ) < 0 )
    //{
    //    printf( "%s - Warning: Unable to set VSync! SDL Error: %s\n", __FUNCTION__, SDL_GetError() );
    //    return false;
    //}

...
    //std::string strWindowTitle = "hellovr - " + m_strDriver + " " + m_strDisplay;
    //SDL_SetWindowTitle( m_pCompanionWindow, strWindowTitle.c_str() );
    
    //// cube array
     //m_iSceneVolumeWidth = m_iSceneVolumeInit;
     //m_iSceneVolumeHeight = m_iSceneVolumeInit;
     //m_iSceneVolumeDepth = m_iSceneVolumeInit;
         
     //m_fScale = 0.3f;
     //m_fScaleSpacing = 4.0f;
 
     //m_fNearClip = 0.1f;
     //m_fFarClip = 30.0f;
 
     //m_iTexture = 0;
     //m_uiVertcount = 0;
...

    //if (!BInitGL())
    //{
    //    printf("%s - Unable to initialize OpenGL!\n", __FUNCTION__);
    //    return false;
    //}

In the CMainApplication::RunMainLoop() method, comment the following (shown as commented):

C++
    //SDL_StartTextInput();
    //SDL_ShowCursor( SDL_DISABLE );
...
    //RenderFrame();
...
//SDL_StopTextInput();

In the CMainApplication::ProcessVREvent(...) method, comment the following (shown as commented):

C++
//SetupRenderModelForTrackedDevice( event.trackedDeviceIndex );

5) Write code in order to grab and print out positional data. To do this, find the method CMainApplication::HandleInput(), and add printPositionalData() method call (not created yet) as follows:

C++
bool CMainApplication::HandleInput()
{

...

    // Process SteamVR events
    vr::VREvent_t event;
    while( m_pHMD->PollNextEvent( &event, sizeof( event ) ) )
    {
        ProcessVREvent( event );
    }

    
    printPositionalData();

    // Process SteamVR controller state
    for( vr::TrackedDeviceIndex_t unDevice = 0; unDevice < vr::k_unMaxTrackedDeviceCount; unDevice++ )
    {
...
}

Now, we need to create this funtion:

//-----------------------------------------------------------------------------
// Purpose: Prints out position (x,y,z) and rotation (qw,qx,qy,qz) into the console.
//-----------------------------------------------------------------------------
void CMainApplication::printPositionalData()
{
 
    // Process SteamVR device states
    for (vr::TrackedDeviceIndex_t unDevice = 0; unDevice < vr::k_unMaxTrackedDeviceCount; unDevice++)
    {
        if (!m_pHMD->IsTrackedDeviceConnected(unDevice))
            continue;
 
        vr::VRControllerState_t state;
        if (m_pHMD->GetControllerState(unDevice, &state, sizeof(state)))
        {
            vr::TrackedDevicePose_t trackedDevicePose;
            vr::TrackedDevicePose_t trackedControllerPose; 
            vr::VRControllerState_t controllerState;            
            vr::HmdMatrix34_t poseMatrix;
            vr::HmdVector3_t position;
            vr::HmdQuaternion_t quaternion;
            vr::ETrackedDeviceClass trackedDeviceClass = vr::VRSystem()->GetTrackedDeviceClass(unDevice);
 
            switch (trackedDeviceClass) {
            case vr::ETrackedDeviceClass::TrackedDeviceClass_HMD:
                vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0, &trackedDevicePose, 1);                
                // print positiona data for the HMD.
                poseMatrix = trackedDevicePose.mDeviceToAbsoluteTracking; // This matrix contains all positional and rotational data.
                position = GetPosition(trackedDevicePose.mDeviceToAbsoluteTracking);
                quaternion = GetRotation(trackedDevicePose.mDeviceToAbsoluteTracking);
 
                printDevicePositionalData("HMD", poseMatrix, position, quaternion);
 
                break;
 
            case vr::ETrackedDeviceClass::TrackedDeviceClass_GenericTracker:                
                vr::VRSystem()->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0, &trackedDevicePose, 1);
                // print positiona data for a general vive tracker.
                break;
 
            case vr::ETrackedDeviceClass::TrackedDeviceClass_Controller:
                vr::VRSystem()->GetControllerStateWithPose(vr::TrackingUniverseStanding, unDevice, &controllerState, 
                    sizeof(controllerState), &trackedControllerPose);
                poseMatrix = trackedControllerPose.mDeviceToAbsoluteTracking; // This matrix contains all positional and rotational data.
                position = GetPosition(trackedControllerPose.mDeviceToAbsoluteTracking);
                quaternion = GetRotation(trackedControllerPose.mDeviceToAbsoluteTracking);
 
                auto trackedControllerRole = vr::VRSystem()->GetControllerRoleForTrackedDeviceIndex(unDevice);
                std::string whichHand = "";
                if (trackedControllerRole == vr::TrackedControllerRole_LeftHand)
                {
                    whichHand = "LeftHand";
                }
                else if (trackedControllerRole == vr::TrackedControllerRole_RightHand)
                {
                    whichHand = "RightHand";
                }
 
                switch (trackedControllerRole)
                {
                case vr::TrackedControllerRole_Invalid:
                    // invalid
                    break;
 
                case vr::TrackedControllerRole_LeftHand:
                case vr::TrackedControllerRole_RightHand:
                    printDevicePositionalData(whichHand.c_str(), poseMatrix, position, quaternion);
 
                    break;
                }
 
                break;
            }
 
        }
    }
 
}

This method uses three other methods: GetRotation(), GetPosition() and printDevicePositionalData(...), we create the first two methods as follows (from Omnifinity github repo):

C++
//-----------------------------------------------------------------------------
// Purpose: Calculates quaternion (qw,qx,qy,qz) representing the rotation
// from: https://github.com/Omnifinity/OpenVR-Tracking-Example/blob/master/HTC%20Lighthouse%20Tracking%20Example/LighthouseTracking.cpp
//-----------------------------------------------------------------------------

vr::HmdQuaternion_t CMainApplication::GetRotation(vr::HmdMatrix34_t matrix) {
    vr::HmdQuaternion_t q;

    q.w = sqrt(fmax(0, 1 + matrix.m[0][0] + matrix.m[1][1] + matrix.m[2][2])) / 2;
    q.x = sqrt(fmax(0, 1 + matrix.m[0][0] - matrix.m[1][1] - matrix.m[2][2])) / 2;
    q.y = sqrt(fmax(0, 1 - matrix.m[0][0] + matrix.m[1][1] - matrix.m[2][2])) / 2;
    q.z = sqrt(fmax(0, 1 - matrix.m[0][0] - matrix.m[1][1] + matrix.m[2][2])) / 2;
    q.x = copysign(q.x, matrix.m[2][1] - matrix.m[1][2]);
    q.y = copysign(q.y, matrix.m[0][2] - matrix.m[2][0]);
    q.z = copysign(q.z, matrix.m[1][0] - matrix.m[0][1]);
    return q;
}
//-----------------------------------------------------------------------------
// Purpose: Extracts position (x,y,z).
// from: https://github.com/Omnifinity/OpenVR-Tracking-Example/blob/master/HTC%20Lighthouse%20Tracking%20Example/LighthouseTracking.cpp
//-----------------------------------------------------------------------------

vr::HmdVector3_t CMainApplication::GetPosition(vr::HmdMatrix34_t matrix) {
    vr::HmdVector3_t vector;

    vector.v[0] = matrix.m[0][3];
    vector.v[1] = matrix.m[1][3];
    vector.v[2] = matrix.m[2][3];

    return vector;
}

And here is the third method printDevicePositionalData() which adds high-resolution time stamps and format data:

//-----------------------------------------------------------------------------
// Purpose: Prints the timestamped data in proper format(x,y,z).
//-----------------------------------------------------------------------------
 
void CMainApplication::printDevicePositionalData(const char * deviceName, vr::HmdMatrix34_t posMatrix, vr::HmdVector3_t position, vr::HmdQuaternion_t quaternion)
{
    LARGE_INTEGER qpc; // Query Performance Counter for Acquiring high-resolution time stamps.
                       // From MSDN: "QPC is typically the best method to use to time-stamp events and 
                       // measure small time intervals that occur on the same system or virtual machine.
    QueryPerformanceCounter(&qpc);
 
    // Print position and quaternion (rotation).
    dprintf("\n%lld, %s, x = %.5f, y = %.5f, z = %.5f, qw = %.5f, qx = %.5f, qy = %.5f, qz = %.5f",
        qpc.QuadPart, deviceName,
        position.v[0], position.v[1], position.v[2],
        quaternion.w, quaternion.x, quaternion.y, quaternion.z);
 
 
    // Uncomment this if you want to print entire transform matrix that contains both position and rotation matrix.
    //dprintf("\n%lld,%s,%.5f,%.5f,%.5f,x: %.5f,%.5f,%.5f,%.5f,y: %.5f,%.5f,%.5f,%.5f,z: %.5f,qw: %.5f,qx: %.5f,qy: %.5f,qz: %.5f",
    //    qpc.QuadPart, whichHand.c_str(),
    //    posMatrix.m[0][0], posMatrix.m[0][1], posMatrix.m[0][2], posMatrix.m[0][3],
    //    posMatrix.m[1][0], posMatrix.m[1][1], posMatrix.m[1][2], posMatrix.m[1][3],
    //    posMatrix.m[2][0], posMatrix.m[2][1], posMatrix.m[2][2], posMatrix.m[2][3],
    //    quaternion.w, quaternion.x, quaternion.y, quaternion.z);
 
}

Now, make sure the SteamVR is running and it can detect your controllers and your headset. Run the application from within Visual Studio (make sure you're running Visual Studio as administrator).

At this stage, you should be able to see the output of the application in the 'Output' window of Visual Studio as follows:

4487146818625, RightHand, x = 0.41616, y = 1.26001, z = -0.22615, qw = 0.22874, qx = -0.96962, qy = 0.03770, qz = 0.07802
4487146818983, HMD, x = 0.35692, y = 1.12997, z = -0.34819, qw = 0.92676, qx = -0.02268, qy = 0.37498, qz = 0.00021
4487146819317, LeftHand, x = 0.47935, y = 1.41869, z = -0.36343, qw = 0.68717, qx = -0.04171, qy = -0.65423, qz = 0.31311
4487146819667, RightHand, x = 0.41616, y = 1.26001, z = -0.22615, qw = 0.22874, qx = -0.96962, qy = 0.03770, qz = 0.07802
4487146820151, HMD, x = 0.35692, y = 1.12997, z = -0.34819, qw = 0.92676, qx = -0.02268, qy = 0.37498, qz = 0.00017
4487146820610, LeftHand, x = 0.47935, y = 1.41869, z = -0.36343, qw = 0.68717, qx = -0.04171, qy = -0.65423, qz = 0.31311
4487146821543, RightHand, x = 0.41616, y = 1.26001, z = -0.22615, qw = 0.22874, qx = -0.96962, qy = 0.03770, qz = 0.07802
4487146822058, HMD, x = 0.35692, y = 1.12997, z = -0.34818, qw = 0.92676, qx = -0.02267, qy = 0.37498, qz = 0.00017
4487146822583, LeftHand, x = 0.47935, y = 1.41869, z = -0.36343, qw = 0.68717, qx = -0.04171, qy = -0.65423, qz = 0.31311

5) Write the outputs into a text file. To do this, run the 'PowerShell' as administrator. Then, cd into the folder containing the binary file:

cd C:\[...]\samples\bin\win32

Afterwards, enter the following command:

C:\[...]\samples\bin\win32> .\hellovr_opengl.exe | tee vroutput.txt

The output is written (at the same time) both on the console and on the file "vroutput.txt" (located in the same folder):

Image 2

Text file "vroutput.txt" :

Image 3

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)