text: //========= Copyright Valve Corporation ============//
  
  #include <SDL.h>
  #include <GL/glew.h>
  #include <SDL_opengl.h>
  #if defined( OSX )
  #include <Foundation/Foundation.h>
  #include <AppKit/AppKit.h>
  #include <OpenGL/glu.h>
  // Apple's version of glut.h #undef's APIENTRY, redefine it
  #define APIENTRY
  #else
  #include <GL/glu.h>
  #endif
  #include <stdio.h>
  #include <string>
  #include <cstdlib>
  
  #include <openvr.h>
  
  #include "shared/lodepng.h"
  #include "shared/Matrices.h"
  #include "shared/pathtools.h"
  
  #if defined(POSIX)
  #include "unistd.h"
  #endif
  
  #ifndef _WIN32
  #define APIENTRY
  #endif
  
  #ifndef _countof
  #define _countof(x) (sizeof(x)/sizeof((x)[0]))
  #endif
  
  void ThreadSleep( unsigned long nMilliseconds )
  {
  #if defined(_WIN32)
  	::Sleep( nMilliseconds );
  #elif defined(POSIX)
  	usleep( nMilliseconds * 1000 );
  #endif
  }
  
  class CGLRenderModel
  {
  public:
  	CGLRenderModel( const std::string & sRenderModelName );
  	~CGLRenderModel();
  
  	bool BInit( const vr::RenderModel_t & vrModel, const vr::RenderModel_TextureMap_t & vrDiffuseTexture );
  	void Cleanup();
  	void Draw();
  	const std::string & GetName() const { return m_sModelName; }
  
  private:
  	GLuint m_glVertBuffer;
  	GLuint m_glIndexBuffer;
  	GLuint m_glVertArray;
  	GLuint m_glTexture;
  	GLsizei m_unVertexCount;
  	std::string m_sModelName;
  };
  
  static bool g_bPrintf = true;
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //------------------------------------------------------------------------------
  class CMainApplication
  {
  public:
  	CMainApplication( int argc, char *argv[] );
  	virtual ~CMainApplication();
  
  	bool BInit();
  	bool BInitGL();
  	bool BInitCompositor();
  
  	void SetupRenderModels();
  
  	void Shutdown();
  
  	void RunMainLoop();
  
      vr::HmdQuaternion_t GetRotation(vr::HmdMatrix34_t matrix);
      vr::HmdVector3_t GetPosition(vr::HmdMatrix34_t matrix);
      void printPositionalData();
      void printDevicePositionalData(const char* deviceName, vr::HmdMatrix34_t poseMatrix, vr::HmdVector3_t position, vr::HmdQuaternion_t quaternion);
  	bool HandleInput();
  	void ProcessVREvent( const vr::VREvent_t & event );
  	void RenderFrame();
  
  	bool SetupTexturemaps();
  
  	void SetupScene();
  	void AddCubeToScene( Matrix4 mat, std::vector<float> &vertdata );
  	void AddCubeVertex( float fl0, float fl1, float fl2, float fl3, float fl4, std::vector<float> &vertdata );
  
  	void RenderControllerAxes();
  
  	bool SetupStereoRenderTargets();
  	void SetupCompanionWindow();
  	void SetupCameras();
  
  	void RenderStereoTargets();
  	void RenderCompanionWindow();
  	void RenderScene( vr::Hmd_Eye nEye );
  
  	Matrix4 GetHMDMatrixProjectionEye( vr::Hmd_Eye nEye );
  	Matrix4 GetHMDMatrixPoseEye( vr::Hmd_Eye nEye );
  	Matrix4 GetCurrentViewProjectionMatrix( vr::Hmd_Eye nEye );
  	void UpdateHMDMatrixPose();
  
  	Matrix4 ConvertSteamVRMatrixToMatrix4( const vr::HmdMatrix34_t &matPose );
  
  	GLuint CompileGLShader( const char *pchShaderName, const char *pchVertexShader, const char *pchFragmentShader );
  	bool CreateAllShaders();
  
  	void SetupRenderModelForTrackedDevice( vr::TrackedDeviceIndex_t unTrackedDeviceIndex );
  	CGLRenderModel *FindOrLoadRenderModel( const char *pchRenderModelName );
  
  private: 
  	bool m_bDebugOpenGL;
  	bool m_bVerbose;
  	bool m_bPerf;
  	bool m_bVblank;
  	bool m_bGlFinishHack;
  
  	vr::IVRSystem *m_pHMD;
  	vr::IVRRenderModels *m_pRenderModels;
  	std::string m_strDriver;
  	std::string m_strDisplay;
  	vr::TrackedDevicePose_t m_rTrackedDevicePose[ vr::k_unMaxTrackedDeviceCount ];
  	Matrix4 m_rmat4DevicePose[ vr::k_unMaxTrackedDeviceCount ];
  	bool m_rbShowTrackedDevice[ vr::k_unMaxTrackedDeviceCount ];
  
  private: // SDL bookkeeping
  	SDL_Window *m_pCompanionWindow;
  	uint32_t m_nCompanionWindowWidth;
  	uint32_t m_nCompanionWindowHeight;
  
  	SDL_GLContext m_pContext;
  
  private: // OpenGL bookkeeping
  	int m_iTrackedControllerCount;
  	int m_iTrackedControllerCount_Last;
  	int m_iValidPoseCount;
  	int m_iValidPoseCount_Last;
  	bool m_bShowCubes;
  
  	std::string m_strPoseClasses;                            // what classes we saw poses for this frame
  	char m_rDevClassChar[ vr::k_unMaxTrackedDeviceCount ];   // for each device, a character representing its class
  
  	int m_iSceneVolumeWidth;
  	int m_iSceneVolumeHeight;
  	int m_iSceneVolumeDepth;
  	float m_fScaleSpacing;
  	float m_fScale;
  	
  	int m_iSceneVolumeInit;                                  // if you want something other than the default 20x20x20
  	
  	float m_fNearClip;
  	float m_fFarClip;
  
  	GLuint m_iTexture;
  
  	unsigned int m_uiVertcount;
  
  	GLuint m_glSceneVertBuffer;
  	GLuint m_unSceneVAO;
  	GLuint m_unCompanionWindowVAO;
  	GLuint m_glCompanionWindowIDVertBuffer;
  	GLuint m_glCompanionWindowIDIndexBuffer;
  	unsigned int m_uiCompanionWindowIndexSize;
  
  	GLuint m_glControllerVertBuffer;
  	GLuint m_unControllerVAO;
  	unsigned int m_uiControllerVertcount;
  
  	Matrix4 m_mat4HMDPose;
  	Matrix4 m_mat4eyePosLeft;
  	Matrix4 m_mat4eyePosRight;
  
  	Matrix4 m_mat4ProjectionCenter;
  	Matrix4 m_mat4ProjectionLeft;
  	Matrix4 m_mat4ProjectionRight;
  
  	struct VertexDataScene
  	{
  		Vector3 position;
  		Vector2 texCoord;
  	};
  
  	struct VertexDataWindow
  	{
  		Vector2 position;
  		Vector2 texCoord;
  
  		VertexDataWindow( const Vector2 & pos, const Vector2 tex ) :  position(pos), texCoord(tex) {	}
  	};
  
  	GLuint m_unSceneProgramID;
  	GLuint m_unCompanionWindowProgramID;
  	GLuint m_unControllerTransformProgramID;
  	GLuint m_unRenderModelProgramID;
  
  	GLint m_nSceneMatrixLocation;
  	GLint m_nControllerMatrixLocation;
  	GLint m_nRenderModelMatrixLocation;
  
  	struct FramebufferDesc
  	{
  		GLuint m_nDepthBufferId;
  		GLuint m_nRenderTextureId;
  		GLuint m_nRenderFramebufferId;
  		GLuint m_nResolveTextureId;
  		GLuint m_nResolveFramebufferId;
  	};
  	FramebufferDesc leftEyeDesc;
  	FramebufferDesc rightEyeDesc;
  
  	bool CreateFrameBuffer( int nWidth, int nHeight, FramebufferDesc &framebufferDesc );
  	
  	uint32_t m_nRenderWidth;
  	uint32_t m_nRenderHeight;
  
  	std::vector< CGLRenderModel * > m_vecRenderModels;
  	CGLRenderModel *m_rTrackedDeviceToRenderModel[ vr::k_unMaxTrackedDeviceCount ];
  };
  
  //-----------------------------------------------------------------------------
  // Purpose: Outputs a set of optional arguments to debugging output, using
  //          the printf format setting specified in fmt*.
  //-----------------------------------------------------------------------------
  void dprintf( const char *fmt, ... )
  {
  	va_list args;
  	char buffer[ 2048 ];
  
  	va_start( args, fmt );
  	vsprintf_s( buffer, fmt, args );
  	va_end( args );
  
  	if ( g_bPrintf )
  		printf( "%s", buffer );
  
  	OutputDebugStringA( buffer );
  }
  
  //-----------------------------------------------------------------------------
  // Purpose: Constructor
  //-----------------------------------------------------------------------------
  CMainApplication::CMainApplication( int argc, char *argv[] )
  	: m_pCompanionWindow(NULL)
  	, m_pContext(NULL)
  	, m_nCompanionWindowWidth( 640 )
  	, m_nCompanionWindowHeight( 320 )
  	, m_unSceneProgramID( 0 )
  	, m_unCompanionWindowProgramID( 0 )
  	, m_unControllerTransformProgramID( 0 )
  	, m_unRenderModelProgramID( 0 )
  	, m_pHMD( NULL )
  	, m_pRenderModels( NULL )
  	, m_bDebugOpenGL( false )
  	, m_bVerbose( false )
  	, m_bPerf( false )
  	, m_bVblank( false )
  	, m_bGlFinishHack( true )
  	, m_glControllerVertBuffer( 0 )
  	, m_unControllerVAO( 0 )
  	, m_unSceneVAO( 0 )
  	, m_nSceneMatrixLocation( -1 )
  	, m_nControllerMatrixLocation( -1 )
  	, m_nRenderModelMatrixLocation( -1 )
  	, m_iTrackedControllerCount( 0 )
  	, m_iTrackedControllerCount_Last( -1 )
  	, m_iValidPoseCount( 0 )
  	, m_iValidPoseCount_Last( -1 )
  	, m_iSceneVolumeInit( 20 )
  	, m_strPoseClasses("")
  	, m_bShowCubes( true )
  {
  
  	for( int i = 1; i < argc; i++ )
  	{
  		if( !stricmp( argv[i], "-gldebug" ) )
  		{
  			m_bDebugOpenGL = true;
  		}
  		else if( !stricmp( argv[i], "-verbose" ) )
  		{
  			m_bVerbose = true;
  		}
  		else if( !stricmp( argv[i], "-novblank" ) )
  		{
  			m_bVblank = false;
  		}
  		else if( !stricmp( argv[i], "-noglfinishhack" ) )
  		{
  			m_bGlFinishHack = false;
  		}
  		else if( !stricmp( argv[i], "-noprintf" ) )
  		{
  			g_bPrintf = false;
  		}
  		else if ( !stricmp( argv[i], "-cubevolume" ) && ( argc > i + 1 ) && ( *argv[ i + 1 ] != '-' ) )
  		{
  			m_iSceneVolumeInit = atoi( argv[ i + 1 ] );
  			i++;
  		}
  	}
  	// other initialization tasks are done in BInit
  	memset(m_rDevClassChar, 0, sizeof(m_rDevClassChar));
  };
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Destructor
  //-----------------------------------------------------------------------------
  CMainApplication::~CMainApplication()
  {
  	// work is done in Shutdown
  	dprintf( "Shutdown" );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Helper to get a string from a tracked device property and turn it
  //			into a std::string
  //-----------------------------------------------------------------------------
  std::string GetTrackedDeviceString( vr::IVRSystem *pHmd, vr::TrackedDeviceIndex_t unDevice, vr::TrackedDeviceProperty prop, vr::TrackedPropertyError *peError = NULL )
  {
  	uint32_t unRequiredBufferLen = pHmd->GetStringTrackedDeviceProperty( unDevice, prop, NULL, 0, peError );
  	if( unRequiredBufferLen == 0 )
  		return "";
  
  	char *pchBuffer = new char[ unRequiredBufferLen ];
  	unRequiredBufferLen = pHmd->GetStringTrackedDeviceProperty( unDevice, prop, pchBuffer, unRequiredBufferLen, peError );
  	std::string sResult = pchBuffer;
  	delete [] pchBuffer;
  	return sResult;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  bool CMainApplication::BInit()
  {
  	if ( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER ) < 0 )
  	{
  		printf("%s - SDL could not initialize! SDL Error: %s\n", __FUNCTION__, SDL_GetError());
  		return false;
  	}
  
  	// Loading the SteamVR Runtime
  	vr::EVRInitError eError = vr::VRInitError_None;
      m_pHMD = vr::VR_Init(&eError, vr::VRApplication_Background);//vr::VRApplication_Scene );
  
  	if ( eError != vr::VRInitError_None )
  	{
  		m_pHMD = NULL;
  		char buf[1024];
  		sprintf_s( buf, sizeof( buf ), "Unable to init VR runtime: %s", vr::VR_GetVRInitErrorAsEnglishDescription( eError ) );
  		SDL_ShowSimpleMessageBox( SDL_MESSAGEBOX_ERROR, "VR_Init Failed", buf, NULL );
  		return false;
  	}
  
  
  	//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;
  	//}
  
  
  	m_strDriver = "No Driver";
  	m_strDisplay = "No Display";
  
  	m_strDriver = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String );
  	m_strDisplay = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SerialNumber_String );
  
  	//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;
   
  // 		m_MillisecondsTimer.start(1, this);
  // 		m_SecondsTimer.start(1000, this);
  	
  	//if (!BInitGL())
  	//{
  	//	printf("%s - Unable to initialize OpenGL!\n", __FUNCTION__);
  	//	return false;
  	//}
  
  	if (!BInitCompositor())
  	{
  		printf("%s - Failed to initialize VR Compositor!\n", __FUNCTION__);
  		return false;
  	}
  
  	return true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Outputs the string in message to debugging output.
  //          All other parameters are ignored.
  //          Does not return any meaningful value or reference.
  //-----------------------------------------------------------------------------
  void APIENTRY DebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* message, const void* userParam)
  {
  	dprintf( "GL Error: %s\n", message );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Initialize OpenGL. Returns true if OpenGL has been successfully
  //          initialized, false if shaders could not be created.
  //          If failure occurred in a module other than shaders, the function
  //          may return true or throw an error. 
  //-----------------------------------------------------------------------------
  bool CMainApplication::BInitGL()
  {
  	if( m_bDebugOpenGL )
  	{
  		glDebugMessageCallback( (GLDEBUGPROC)DebugCallback, nullptr);
  		glDebugMessageControl( GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE );
  		glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
  	}
  
  	if( !CreateAllShaders() )
  		return false;
  
  	SetupTexturemaps();
  	SetupScene();
  	SetupCameras();
  	SetupStereoRenderTargets();
  	SetupCompanionWindow();
  	SetupRenderModels();
  
  	return true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Initialize Compositor. Returns true if the compositor was
  //          successfully initialized, false otherwise.
  //-----------------------------------------------------------------------------
  bool CMainApplication::BInitCompositor()
  {
  	vr::EVRInitError peError = vr::VRInitError_None;
  
  	if ( !vr::VRCompositor() )
  	{
  		printf( "Compositor initialization failed. See log file for details\n" );
  		return false;
  	}
  
  	return true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::Shutdown()
  {
  	if( m_pHMD )
  	{
  		vr::VR_Shutdown();
  		m_pHMD = NULL;
  	}
  
  	for( std::vector< CGLRenderModel * >::iterator i = m_vecRenderModels.begin(); i != m_vecRenderModels.end(); i++ )
  	{
  		delete (*i);
  	}
  	m_vecRenderModels.clear();
  	
  	if( m_pContext )
  	{
  		glDebugMessageControl( GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE );
  		glDebugMessageCallback(nullptr, nullptr);
  		glDeleteBuffers(1, &m_glSceneVertBuffer);
  
  		if ( m_unSceneProgramID )
          {
          glDeleteProgram(m_unSceneProgramID);
          }
          if (m_unControllerTransformProgramID)
          {
              glDeleteProgram(m_unControllerTransformProgramID);
          }
          if (m_unRenderModelProgramID)
          {
              glDeleteProgram(m_unRenderModelProgramID);
          }
          if (m_unCompanionWindowProgramID)
          {
              glDeleteProgram(m_unCompanionWindowProgramID);
          }
  
          glDeleteRenderbuffers(1, &leftEyeDesc.m_nDepthBufferId);
          glDeleteTextures(1, &leftEyeDesc.m_nRenderTextureId);
          glDeleteFramebuffers(1, &leftEyeDesc.m_nRenderFramebufferId);
          glDeleteTextures(1, &leftEyeDesc.m_nResolveTextureId);
          glDeleteFramebuffers(1, &leftEyeDesc.m_nResolveFramebufferId);
  
          glDeleteRenderbuffers(1, &rightEyeDesc.m_nDepthBufferId);
          glDeleteTextures(1, &rightEyeDesc.m_nRenderTextureId);
          glDeleteFramebuffers(1, &rightEyeDesc.m_nRenderFramebufferId);
          glDeleteTextures(1, &rightEyeDesc.m_nResolveTextureId);
          glDeleteFramebuffers(1, &rightEyeDesc.m_nResolveFramebufferId);
  
          if (m_unCompanionWindowVAO != 0)
          {
              glDeleteVertexArrays(1, &m_unCompanionWindowVAO);
          }
          if (m_unSceneVAO != 0)
          {
              glDeleteVertexArrays(1, &m_unSceneVAO);
          }
          if (m_unControllerVAO != 0)
          {
              glDeleteVertexArrays(1, &m_unControllerVAO);
          }
      }
  
      if (m_pCompanionWindow)
      {
          SDL_DestroyWindow(m_pCompanionWindow);
          m_pCompanionWindow = NULL;
      }
  
      SDL_Quit();
  }
  //-----------------------------------------------------------------------------
  // 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;
  }
  //-----------------------------------------------------------------------------
  // 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);
  
  }
  //-----------------------------------------------------------------------------
  // 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;
              }
  
          }
      }
  
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  bool CMainApplication::HandleInput()
  {
  	SDL_Event sdlEvent;
  	bool bRet = false;
  
  	while ( SDL_PollEvent( &sdlEvent ) != 0 )
  	{
  		if ( sdlEvent.type == SDL_QUIT )
  		{
  			bRet = true;
  		}
  		else if ( sdlEvent.type == SDL_KEYDOWN )
  		{
  			if ( sdlEvent.key.keysym.sym == SDLK_ESCAPE 
  			     || sdlEvent.key.keysym.sym == SDLK_q )
  			{
  				bRet = true;
  			}
  			if( sdlEvent.key.keysym.sym == SDLK_c )
  			{
  				m_bShowCubes = !m_bShowCubes;
  			}
  		}
  	}
  
  	// 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++ )
  	{
  		vr::VRControllerState_t state;
  		if( m_pHMD->GetControllerState( unDevice, &state, sizeof(state) ) )
  		{
  			m_rbShowTrackedDevice[ unDevice ] = state.ulButtonPressed == 0;
  		}
  	}
  
  	return bRet;
  }
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::RunMainLoop()
  {
  	bool bQuit = false;
      // we don't need the followings for a background process application
  	//SDL_StartTextInput();
  	//SDL_ShowCursor( SDL_DISABLE );
  
  	while ( !bQuit )
  	{
  		bQuit = HandleInput();
  
          // we don't need the followings for a background process application
  		//RenderFrame();
  	}
  
  	//SDL_StopTextInput();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Processes a single VR event
  //-----------------------------------------------------------------------------
  void CMainApplication::ProcessVREvent( const vr::VREvent_t & event )
  {
  	switch( event.eventType )
  	{
  	case vr::VREvent_TrackedDeviceActivated:
  		{
  			//SetupRenderModelForTrackedDevice( event.trackedDeviceIndex );
  			dprintf( "Device %u attached.\n", event.trackedDeviceIndex );
  		}
  		break;
  	case vr::VREvent_TrackedDeviceDeactivated:
  		{
  			dprintf( "Device %u detached.\n", event.trackedDeviceIndex );
  		}
  		break;
  	case vr::VREvent_TrackedDeviceUpdated:
  		{
  			dprintf( "Device %u updated.\n", event.trackedDeviceIndex );
  		}
  		break;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::RenderFrame()
  {
  	// for now as fast as possible
  	if ( m_pHMD )
  	{
  		RenderControllerAxes();
  		RenderStereoTargets();
  		RenderCompanionWindow();
  
  		vr::Texture_t leftEyeTexture = {(void*)(uintptr_t)leftEyeDesc.m_nResolveTextureId, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
  		vr::VRCompositor()->Submit(vr::Eye_Left, &leftEyeTexture );
  		vr::Texture_t rightEyeTexture = {(void*)(uintptr_t)rightEyeDesc.m_nResolveTextureId, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
  		vr::VRCompositor()->Submit(vr::Eye_Right, &rightEyeTexture );
  	}
  
  	if ( m_bVblank && m_bGlFinishHack )
  	{
  		//$ HACKHACK. From gpuview profiling, it looks like there is a bug where two renders and a present
  		// happen right before and after the vsync causing all kinds of jittering issues. This glFinish()
  		// appears to clear that up. Temporary fix while I try to get nvidia to investigate this problem.
  		// 1/29/2014 mikesart
  		glFinish();
  	}
  
  	// SwapWindow
  	{
  		SDL_GL_SwapWindow( m_pCompanionWindow );
  	}
  
  	// Clear
  	{
  		// We want to make sure the glFinish waits for the entire present to complete, not just the submission
  		// of the command. So, we do a clear here right here so the glFinish will wait fully for the swap.
  		glClearColor( 0, 0, 0, 1 );
  		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  	}
  
  	// Flush and wait for swap.
  	if ( m_bVblank )
  	{
  		glFlush();
  		glFinish();
  	}
  
  	// Spew out the controller and pose count whenever they change.
  	if ( m_iTrackedControllerCount != m_iTrackedControllerCount_Last || m_iValidPoseCount != m_iValidPoseCount_Last )
  	{
  		m_iValidPoseCount_Last = m_iValidPoseCount;
  		m_iTrackedControllerCount_Last = m_iTrackedControllerCount;
  		
  		//dprintf( "PoseCount:%d(%s) Controllers:%d\n", m_iValidPoseCount, m_strPoseClasses.c_str(), m_iTrackedControllerCount );
  	}
  
  	UpdateHMDMatrixPose();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Compiles a GL shader program and returns the handle. Returns 0 if
  //			the shader couldn't be compiled for some reason.
  //-----------------------------------------------------------------------------
  GLuint CMainApplication::CompileGLShader( const char *pchShaderName, const char *pchVertexShader, const char *pchFragmentShader )
  {
  	GLuint unProgramID = glCreateProgram();
  
  	GLuint nSceneVertexShader = glCreateShader(GL_VERTEX_SHADER);
  	glShaderSource( nSceneVertexShader, 1, &pchVertexShader, NULL);
  	glCompileShader( nSceneVertexShader );
  
  	GLint vShaderCompiled = GL_FALSE;
  	glGetShaderiv( nSceneVertexShader, GL_COMPILE_STATUS, &vShaderCompiled);
  	if ( vShaderCompiled != GL_TRUE)
  	{
  		dprintf("%s - Unable to compile vertex shader %d!\n", pchShaderName, nSceneVertexShader);
  		glDeleteProgram( unProgramID );
  		glDeleteShader( nSceneVertexShader );
  		return 0;
  	}
  	glAttachShader( unProgramID, nSceneVertexShader);
  	glDeleteShader( nSceneVertexShader ); // the program hangs onto this once it's attached
  
  	GLuint  nSceneFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
  	glShaderSource( nSceneFragmentShader, 1, &pchFragmentShader, NULL);
  	glCompileShader( nSceneFragmentShader );
  
  	GLint fShaderCompiled = GL_FALSE;
  	glGetShaderiv( nSceneFragmentShader, GL_COMPILE_STATUS, &fShaderCompiled);
  	if (fShaderCompiled != GL_TRUE)
  	{
  		dprintf("%s - Unable to compile fragment shader %d!\n", pchShaderName, nSceneFragmentShader );
  		glDeleteProgram( unProgramID );
  		glDeleteShader( nSceneFragmentShader );
  		return 0;	
  	}
  
  	glAttachShader( unProgramID, nSceneFragmentShader );
  	glDeleteShader( nSceneFragmentShader ); // the program hangs onto this once it's attached
  
  	glLinkProgram( unProgramID );
  
  	GLint programSuccess = GL_TRUE;
  	glGetProgramiv( unProgramID, GL_LINK_STATUS, &programSuccess);
  	if ( programSuccess != GL_TRUE )
  	{
  		dprintf("%s - Error linking program %d!\n", pchShaderName, unProgramID);
  		glDeleteProgram( unProgramID );
  		return 0;
  	}
  
  	glUseProgram( unProgramID );
  	glUseProgram( 0 );
  
  	return unProgramID;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Creates all the shaders used by HelloVR SDL
  //-----------------------------------------------------------------------------
  bool CMainApplication::CreateAllShaders()
  {
  	m_unSceneProgramID = CompileGLShader( 
  		"Scene",
  
  		// Vertex Shader
  		"#version 410\n"
  		"uniform mat4 matrix;\n"
  		"layout(location = 0) in vec4 position;\n"
  		"layout(location = 1) in vec2 v2UVcoordsIn;\n"
  		"layout(location = 2) in vec3 v3NormalIn;\n"
  		"out vec2 v2UVcoords;\n"
  		"void main()\n"
  		"{\n"
  		"	v2UVcoords = v2UVcoordsIn;\n"
  		"	gl_Position = matrix * position;\n"
  		"}\n",
  
  		// Fragment Shader
  		"#version 410 core\n"
  		"uniform sampler2D mytexture;\n"
  		"in vec2 v2UVcoords;\n"
  		"out vec4 outputColor;\n"
  		"void main()\n"
  		"{\n"
  		"   outputColor = texture(mytexture, v2UVcoords);\n"
  		"}\n"
  		);
  	m_nSceneMatrixLocation = glGetUniformLocation( m_unSceneProgramID, "matrix" );
  	if( m_nSceneMatrixLocation == -1 )
  	{
  		dprintf( "Unable to find matrix uniform in scene shader\n" );
  		return false;
  	}
  
  	m_unControllerTransformProgramID = CompileGLShader(
  		"Controller",
  
  		// vertex shader
  		"#version 410\n"
  		"uniform mat4 matrix;\n"
  		"layout(location = 0) in vec4 position;\n"
  		"layout(location = 1) in vec3 v3ColorIn;\n"
  		"out vec4 v4Color;\n"
  		"void main()\n"
  		"{\n"
  		"	v4Color.xyz = v3ColorIn; v4Color.a = 1.0;\n"
  		"	gl_Position = matrix * position;\n"
  		"}\n",
  
  		// fragment shader
  		"#version 410\n"
  		"in vec4 v4Color;\n"
  		"out vec4 outputColor;\n"
  		"void main()\n"
  		"{\n"
  		"   outputColor = v4Color;\n"
  		"}\n"
  		);
  	m_nControllerMatrixLocation = glGetUniformLocation( m_unControllerTransformProgramID, "matrix" );
  	if( m_nControllerMatrixLocation == -1 )
  	{
  		dprintf( "Unable to find matrix uniform in controller shader\n" );
  		return false;
  	}
  
  	m_unRenderModelProgramID = CompileGLShader( 
  		"render model",
  
  		// vertex shader
  		"#version 410\n"
  		"uniform mat4 matrix;\n"
  		"layout(location = 0) in vec4 position;\n"
  		"layout(location = 1) in vec3 v3NormalIn;\n"
  		"layout(location = 2) in vec2 v2TexCoordsIn;\n"
  		"out vec2 v2TexCoord;\n"
  		"void main()\n"
  		"{\n"
  		"	v2TexCoord = v2TexCoordsIn;\n"
  		"	gl_Position = matrix * vec4(position.xyz, 1);\n"
  		"}\n",
  
  		//fragment shader
  		"#version 410 core\n"
  		"uniform sampler2D diffuse;\n"
  		"in vec2 v2TexCoord;\n"
  		"out vec4 outputColor;\n"
  		"void main()\n"
  		"{\n"
  		"   outputColor = texture( diffuse, v2TexCoord);\n"
  		"}\n"
  
  		);
  	m_nRenderModelMatrixLocation = glGetUniformLocation( m_unRenderModelProgramID, "matrix" );
  	if( m_nRenderModelMatrixLocation == -1 )
  	{
  		dprintf( "Unable to find matrix uniform in render model shader\n" );
  		return false;
  	}
  
  	m_unCompanionWindowProgramID = CompileGLShader(
  		"CompanionWindow",
  
  		// vertex shader
  		"#version 410 core\n"
  		"layout(location = 0) in vec4 position;\n"
  		"layout(location = 1) in vec2 v2UVIn;\n"
  		"noperspective out vec2 v2UV;\n"
  		"void main()\n"
  		"{\n"
  		"	v2UV = v2UVIn;\n"
  		"	gl_Position = position;\n"
  		"}\n",
  
  		// fragment shader
  		"#version 410 core\n"
  		"uniform sampler2D mytexture;\n"
  		"noperspective in vec2 v2UV;\n"
  		"out vec4 outputColor;\n"
  		"void main()\n"
  		"{\n"
  		"		outputColor = texture(mytexture, v2UV);\n"
  		"}\n"
  		);
  
  	return m_unSceneProgramID != 0 
  		&& m_unControllerTransformProgramID != 0
  		&& m_unRenderModelProgramID != 0
  		&& m_unCompanionWindowProgramID != 0;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  bool CMainApplication::SetupTexturemaps()
  {
  	std::string sExecutableDirectory = Path_StripFilename( Path_GetExecutablePath() );
  	std::string strFullPath = Path_MakeAbsolute( "../cube_texture.png", sExecutableDirectory );
  	
  	std::vector<unsigned char> imageRGBA;
  	unsigned nImageWidth, nImageHeight;
  	unsigned nError = lodepng::decode( imageRGBA, nImageWidth, nImageHeight, strFullPath.c_str() );
  	
  	if ( nError != 0 )
  		return false;
  
  	glGenTextures(1, &m_iTexture );
  	glBindTexture( GL_TEXTURE_2D, m_iTexture );
  
  	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, nImageWidth, nImageHeight,
  		0, GL_RGBA, GL_UNSIGNED_BYTE, &imageRGBA[0] );
  
  	glGenerateMipmap(GL_TEXTURE_2D);
  
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
  
  	GLfloat fLargest;
  	glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
  	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
  	 	
  	glBindTexture( GL_TEXTURE_2D, 0 );
  
  	return ( m_iTexture != 0 );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: create a sea of cubes
  //-----------------------------------------------------------------------------
  void CMainApplication::SetupScene()
  {
  	if ( !m_pHMD )
  		return;
  
  	std::vector<float> vertdataarray;
  
  	Matrix4 matScale;
  	matScale.scale( m_fScale, m_fScale, m_fScale );
  	Matrix4 matTransform;
  	matTransform.translate(
  		-( (float)m_iSceneVolumeWidth * m_fScaleSpacing ) / 2.f,
  		-( (float)m_iSceneVolumeHeight * m_fScaleSpacing ) / 2.f,
  		-( (float)m_iSceneVolumeDepth * m_fScaleSpacing ) / 2.f);
  	
  	Matrix4 mat = matScale * matTransform;
  
  	for( int z = 0; z< m_iSceneVolumeDepth; z++ )
  	{
  		for( int y = 0; y< m_iSceneVolumeHeight; y++ )
  		{
  			for( int x = 0; x< m_iSceneVolumeWidth; x++ )
  			{
  				AddCubeToScene( mat, vertdataarray );
  				mat = mat * Matrix4().translate( m_fScaleSpacing, 0, 0 );
  			}
  			mat = mat * Matrix4().translate( -((float)m_iSceneVolumeWidth) * m_fScaleSpacing, m_fScaleSpacing, 0 );
  		}
  		mat = mat * Matrix4().translate( 0, -((float)m_iSceneVolumeHeight) * m_fScaleSpacing, m_fScaleSpacing );
  	}
  	m_uiVertcount = vertdataarray.size()/5;
  	
  	glGenVertexArrays( 1, &m_unSceneVAO );
  	glBindVertexArray( m_unSceneVAO );
  
  	glGenBuffers( 1, &m_glSceneVertBuffer );
  	glBindBuffer( GL_ARRAY_BUFFER, m_glSceneVertBuffer );
  	glBufferData( GL_ARRAY_BUFFER, sizeof(float) * vertdataarray.size(), &vertdataarray[0], GL_STATIC_DRAW);
  
  	GLsizei stride = sizeof(VertexDataScene);
  	uintptr_t offset = 0;
  
  	glEnableVertexAttribArray( 0 );
  	glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride , (const void *)offset);
  
  	offset += sizeof(Vector3);
  	glEnableVertexAttribArray( 1 );
  	glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, stride, (const void *)offset);
  
  	glBindVertexArray( 0 );
  	glDisableVertexAttribArray(0);
  	glDisableVertexAttribArray(1);
  
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::AddCubeVertex( float fl0, float fl1, float fl2, float fl3, float fl4, std::vector<float> &vertdata )
  {
  	vertdata.push_back( fl0 );
  	vertdata.push_back( fl1 );
  	vertdata.push_back( fl2 );
  	vertdata.push_back( fl3 );
  	vertdata.push_back( fl4 );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::AddCubeToScene( Matrix4 mat, std::vector<float> &vertdata )
  {
  	// Matrix4 mat( outermat.data() );
  
  	Vector4 A = mat * Vector4( 0, 0, 0, 1 );
  	Vector4 B = mat * Vector4( 1, 0, 0, 1 );
  	Vector4 C = mat * Vector4( 1, 1, 0, 1 );
  	Vector4 D = mat * Vector4( 0, 1, 0, 1 );
  	Vector4 E = mat * Vector4( 0, 0, 1, 1 );
  	Vector4 F = mat * Vector4( 1, 0, 1, 1 );
  	Vector4 G = mat * Vector4( 1, 1, 1, 1 );
  	Vector4 H = mat * Vector4( 0, 1, 1, 1 );
  
  	// triangles instead of quads
  	AddCubeVertex( E.x, E.y, E.z, 0, 1, vertdata ); //Front
  	AddCubeVertex( F.x, F.y, F.z, 1, 1, vertdata );
  	AddCubeVertex( G.x, G.y, G.z, 1, 0, vertdata );
  	AddCubeVertex( G.x, G.y, G.z, 1, 0, vertdata );
  	AddCubeVertex( H.x, H.y, H.z, 0, 0, vertdata );
  	AddCubeVertex( E.x, E.y, E.z, 0, 1, vertdata );
  					 
  	AddCubeVertex( B.x, B.y, B.z, 0, 1, vertdata ); //Back
  	AddCubeVertex( A.x, A.y, A.z, 1, 1, vertdata );
  	AddCubeVertex( D.x, D.y, D.z, 1, 0, vertdata );
  	AddCubeVertex( D.x, D.y, D.z, 1, 0, vertdata );
  	AddCubeVertex( C.x, C.y, C.z, 0, 0, vertdata );
  	AddCubeVertex( B.x, B.y, B.z, 0, 1, vertdata );
  					
  	AddCubeVertex( H.x, H.y, H.z, 0, 1, vertdata ); //Top
  	AddCubeVertex( G.x, G.y, G.z, 1, 1, vertdata );
  	AddCubeVertex( C.x, C.y, C.z, 1, 0, vertdata );
  	AddCubeVertex( C.x, C.y, C.z, 1, 0, vertdata );
  	AddCubeVertex( D.x, D.y, D.z, 0, 0, vertdata );
  	AddCubeVertex( H.x, H.y, H.z, 0, 1, vertdata );
  				
  	AddCubeVertex( A.x, A.y, A.z, 0, 1, vertdata ); //Bottom
  	AddCubeVertex( B.x, B.y, B.z, 1, 1, vertdata );
  	AddCubeVertex( F.x, F.y, F.z, 1, 0, vertdata );
  	AddCubeVertex( F.x, F.y, F.z, 1, 0, vertdata );
  	AddCubeVertex( E.x, E.y, E.z, 0, 0, vertdata );
  	AddCubeVertex( A.x, A.y, A.z, 0, 1, vertdata );
  					
  	AddCubeVertex( A.x, A.y, A.z, 0, 1, vertdata ); //Left
  	AddCubeVertex( E.x, E.y, E.z, 1, 1, vertdata );
  	AddCubeVertex( H.x, H.y, H.z, 1, 0, vertdata );
  	AddCubeVertex( H.x, H.y, H.z, 1, 0, vertdata );
  	AddCubeVertex( D.x, D.y, D.z, 0, 0, vertdata );
  	AddCubeVertex( A.x, A.y, A.z, 0, 1, vertdata );
  
  	AddCubeVertex( F.x, F.y, F.z, 0, 1, vertdata ); //Right
  	AddCubeVertex( B.x, B.y, B.z, 1, 1, vertdata );
  	AddCubeVertex( C.x, C.y, C.z, 1, 0, vertdata );
  	AddCubeVertex( C.x, C.y, C.z, 1, 0, vertdata );
  	AddCubeVertex( G.x, G.y, G.z, 0, 0, vertdata );
  	AddCubeVertex( F.x, F.y, F.z, 0, 1, vertdata );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Draw all of the controllers as X/Y/Z lines
  //-----------------------------------------------------------------------------
  void CMainApplication::RenderControllerAxes()
  {
  	// don't draw controllers if somebody else has input focus
  	if( m_pHMD->IsInputFocusCapturedByAnotherProcess() )
  		return;
  
  	std::vector<float> vertdataarray;
  
  	m_uiControllerVertcount = 0;
  	m_iTrackedControllerCount = 0;
  
  	for ( vr::TrackedDeviceIndex_t unTrackedDevice = vr::k_unTrackedDeviceIndex_Hmd + 1; unTrackedDevice < vr::k_unMaxTrackedDeviceCount; ++unTrackedDevice )
  	{
  		if ( !m_pHMD->IsTrackedDeviceConnected( unTrackedDevice ) )
  			continue;
  
  		if( m_pHMD->GetTrackedDeviceClass( unTrackedDevice ) != vr::TrackedDeviceClass_Controller )
  			continue;
  
  		m_iTrackedControllerCount += 1;
  
  		if( !m_rTrackedDevicePose[ unTrackedDevice ].bPoseIsValid )
  			continue;
  
  		const Matrix4 & mat = m_rmat4DevicePose[unTrackedDevice];
  
  		Vector4 center = mat * Vector4( 0, 0, 0, 1 );
  
  		for ( int i = 0; i < 3; ++i )
  		{
  			Vector3 color( 0, 0, 0 );
  			Vector4 point( 0, 0, 0, 1 );
  			point[i] += 0.05f;  // offset in X, Y, Z
  			color[i] = 1.0;  // R, G, B
  			point = mat * point;
  			vertdataarray.push_back( center.x );
  			vertdataarray.push_back( center.y );
  			vertdataarray.push_back( center.z );
  
  			vertdataarray.push_back( color.x );
  			vertdataarray.push_back( color.y );
  			vertdataarray.push_back( color.z );
  		
  			vertdataarray.push_back( point.x );
  			vertdataarray.push_back( point.y );
  			vertdataarray.push_back( point.z );
  		
  			vertdataarray.push_back( color.x );
  			vertdataarray.push_back( color.y );
  			vertdataarray.push_back( color.z );
  		
  			m_uiControllerVertcount += 2;
  		}
  
  		Vector4 start = mat * Vector4( 0, 0, -0.02f, 1 );
  		Vector4 end = mat * Vector4( 0, 0, -39.f, 1 );
  		Vector3 color( .92f, .92f, .71f );
  
  		vertdataarray.push_back( start.x );vertdataarray.push_back( start.y );vertdataarray.push_back( start.z );
  		vertdataarray.push_back( color.x );vertdataarray.push_back( color.y );vertdataarray.push_back( color.z );
  
  		vertdataarray.push_back( end.x );vertdataarray.push_back( end.y );vertdataarray.push_back( end.z );
  		vertdataarray.push_back( color.x );vertdataarray.push_back( color.y );vertdataarray.push_back( color.z );
  		m_uiControllerVertcount += 2;
  	}
  
  	// Setup the VAO the first time through.
  	if ( m_unControllerVAO == 0 )
  	{
  		glGenVertexArrays( 1, &m_unControllerVAO );
  		glBindVertexArray( m_unControllerVAO );
  
  		glGenBuffers( 1, &m_glControllerVertBuffer );
  		glBindBuffer( GL_ARRAY_BUFFER, m_glControllerVertBuffer );
  
  		GLuint stride = 2 * 3 * sizeof( float );
  		uintptr_t offset = 0;
  
  		glEnableVertexAttribArray( 0 );
  		glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, stride, (const void *)offset);
  
  		offset += sizeof( Vector3 );
  		glEnableVertexAttribArray( 1 );
  		glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, stride, (const void *)offset);
  
  		glBindVertexArray( 0 );
  	}
  
  	glBindBuffer( GL_ARRAY_BUFFER, m_glControllerVertBuffer );
  
  	// set vertex data if we have some
  	if( vertdataarray.size() > 0 )
  	{
  		//$ TODO: Use glBufferSubData for this...
  		glBufferData( GL_ARRAY_BUFFER, sizeof(float) * vertdataarray.size(), &vertdataarray[0], GL_STREAM_DRAW );
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::SetupCameras()
  {
  	m_mat4ProjectionLeft = GetHMDMatrixProjectionEye( vr::Eye_Left );
  	m_mat4ProjectionRight = GetHMDMatrixProjectionEye( vr::Eye_Right );
  	m_mat4eyePosLeft = GetHMDMatrixPoseEye( vr::Eye_Left );
  	m_mat4eyePosRight = GetHMDMatrixPoseEye( vr::Eye_Right );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Creates a frame buffer. Returns true if the buffer was set up.
  //          Returns false if the setup failed.
  //-----------------------------------------------------------------------------
  bool CMainApplication::CreateFrameBuffer( int nWidth, int nHeight, FramebufferDesc &framebufferDesc )
  {
  	glGenFramebuffers(1, &framebufferDesc.m_nRenderFramebufferId );
  	glBindFramebuffer(GL_FRAMEBUFFER, framebufferDesc.m_nRenderFramebufferId);
  
  	glGenRenderbuffers(1, &framebufferDesc.m_nDepthBufferId);
  	glBindRenderbuffer(GL_RENDERBUFFER, framebufferDesc.m_nDepthBufferId);
  	glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT, nWidth, nHeight );
  	glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,	framebufferDesc.m_nDepthBufferId );
  
  	glGenTextures(1, &framebufferDesc.m_nRenderTextureId );
  	glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, framebufferDesc.m_nRenderTextureId );
  	glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, nWidth, nHeight, true);
  	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, framebufferDesc.m_nRenderTextureId, 0);
  
  	glGenFramebuffers(1, &framebufferDesc.m_nResolveFramebufferId );
  	glBindFramebuffer(GL_FRAMEBUFFER, framebufferDesc.m_nResolveFramebufferId);
  
  	glGenTextures(1, &framebufferDesc.m_nResolveTextureId );
  	glBindTexture(GL_TEXTURE_2D, framebufferDesc.m_nResolveTextureId );
  	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
  	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
  	glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, framebufferDesc.m_nResolveTextureId, 0);
  
  	// check FBO status
  	GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
  	if (status != GL_FRAMEBUFFER_COMPLETE)
  	{
  		return false;
  	}
  
  	glBindFramebuffer( GL_FRAMEBUFFER, 0 );
  
  	return true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  bool CMainApplication::SetupStereoRenderTargets()
  {
  	if ( !m_pHMD )
  		return false;
  
  	m_pHMD->GetRecommendedRenderTargetSize( &m_nRenderWidth, &m_nRenderHeight );
  
  	CreateFrameBuffer( m_nRenderWidth, m_nRenderHeight, leftEyeDesc );
  	CreateFrameBuffer( m_nRenderWidth, m_nRenderHeight, rightEyeDesc );
  	
  	return true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::SetupCompanionWindow()
  {
  	if ( !m_pHMD )
  		return;
  
  	std::vector<VertexDataWindow> vVerts;
  
  	// left eye verts
  	vVerts.push_back( VertexDataWindow( Vector2(-1, -1), Vector2(0, 1)) );
  	vVerts.push_back( VertexDataWindow( Vector2(0, -1), Vector2(1, 1)) );
  	vVerts.push_back( VertexDataWindow( Vector2(-1, 1), Vector2(0, 0)) );
  	vVerts.push_back( VertexDataWindow( Vector2(0, 1), Vector2(1, 0)) );
  
  	// right eye verts
  	vVerts.push_back( VertexDataWindow( Vector2(0, -1), Vector2(0, 1)) );
  	vVerts.push_back( VertexDataWindow( Vector2(1, -1), Vector2(1, 1)) );
  	vVerts.push_back( VertexDataWindow( Vector2(0, 1), Vector2(0, 0)) );
  	vVerts.push_back( VertexDataWindow( Vector2(1, 1), Vector2(1, 0)) );
  
  	GLushort vIndices[] = { 0, 1, 3,   0, 3, 2,   4, 5, 7,   4, 7, 6};
  	m_uiCompanionWindowIndexSize = _countof(vIndices);
  
  	glGenVertexArrays( 1, &m_unCompanionWindowVAO );
  	glBindVertexArray( m_unCompanionWindowVAO );
  
  	glGenBuffers( 1, &m_glCompanionWindowIDVertBuffer );
  	glBindBuffer( GL_ARRAY_BUFFER, m_glCompanionWindowIDVertBuffer );
  	glBufferData( GL_ARRAY_BUFFER, vVerts.size()*sizeof(VertexDataWindow), &vVerts[0], GL_STATIC_DRAW );
  
  	glGenBuffers( 1, &m_glCompanionWindowIDIndexBuffer );
  	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_glCompanionWindowIDIndexBuffer );
  	glBufferData( GL_ELEMENT_ARRAY_BUFFER, m_uiCompanionWindowIndexSize*sizeof(GLushort), &vIndices[0], GL_STATIC_DRAW );
  
  	glEnableVertexAttribArray( 0 );
  	glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(VertexDataWindow), (void *)offsetof( VertexDataWindow, position ) );
  
  	glEnableVertexAttribArray( 1 );
  	glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(VertexDataWindow), (void *)offsetof( VertexDataWindow, texCoord ) );
  
  	glBindVertexArray( 0 );
  
  	glDisableVertexAttribArray(0);
  	glDisableVertexAttribArray(1);
  
  	glBindBuffer(GL_ARRAY_BUFFER, 0);
  	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::RenderStereoTargets()
  {
  	glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
  	glEnable( GL_MULTISAMPLE );
  
  	// Left Eye
  	glBindFramebuffer( GL_FRAMEBUFFER, leftEyeDesc.m_nRenderFramebufferId );
   	glViewport(0, 0, m_nRenderWidth, m_nRenderHeight );
   	RenderScene( vr::Eye_Left );
   	glBindFramebuffer( GL_FRAMEBUFFER, 0 );
  	
  	glDisable( GL_MULTISAMPLE );
  	 	
   	glBindFramebuffer(GL_READ_FRAMEBUFFER, leftEyeDesc.m_nRenderFramebufferId);
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, leftEyeDesc.m_nResolveFramebufferId );
  
      glBlitFramebuffer( 0, 0, m_nRenderWidth, m_nRenderHeight, 0, 0, m_nRenderWidth, m_nRenderHeight, 
  		GL_COLOR_BUFFER_BIT,
   		GL_LINEAR );
  
   	glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0 );	
  
  	glEnable( GL_MULTISAMPLE );
  
  	// Right Eye
  	glBindFramebuffer( GL_FRAMEBUFFER, rightEyeDesc.m_nRenderFramebufferId );
   	glViewport(0, 0, m_nRenderWidth, m_nRenderHeight );
   	RenderScene( vr::Eye_Right );
   	glBindFramebuffer( GL_FRAMEBUFFER, 0 );
   	
  	glDisable( GL_MULTISAMPLE );
  
   	glBindFramebuffer(GL_READ_FRAMEBUFFER, rightEyeDesc.m_nRenderFramebufferId );
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, rightEyeDesc.m_nResolveFramebufferId );
  	
      glBlitFramebuffer( 0, 0, m_nRenderWidth, m_nRenderHeight, 0, 0, m_nRenderWidth, m_nRenderHeight, 
  		GL_COLOR_BUFFER_BIT,
   		GL_LINEAR  );
  
   	glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
      glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0 );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Renders a scene with respect to nEye.
  //-----------------------------------------------------------------------------
  void CMainApplication::RenderScene( vr::Hmd_Eye nEye )
  {
  	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  	glEnable(GL_DEPTH_TEST);
  
  	if( m_bShowCubes )
  	{
  		glUseProgram( m_unSceneProgramID );
  		glUniformMatrix4fv( m_nSceneMatrixLocation, 1, GL_FALSE, GetCurrentViewProjectionMatrix( nEye ).get() );
  		glBindVertexArray( m_unSceneVAO );
  		glBindTexture( GL_TEXTURE_2D, m_iTexture );
  		glDrawArrays( GL_TRIANGLES, 0, m_uiVertcount );
  		glBindVertexArray( 0 );
  	}
  
  	bool bIsInputCapturedByAnotherProcess = m_pHMD->IsInputFocusCapturedByAnotherProcess();
  
  	if( !bIsInputCapturedByAnotherProcess )
  	{
  		// draw the controller axis lines
  		glUseProgram( m_unControllerTransformProgramID );
  		glUniformMatrix4fv( m_nControllerMatrixLocation, 1, GL_FALSE, GetCurrentViewProjectionMatrix( nEye ).get() );
  		glBindVertexArray( m_unControllerVAO );
  		glDrawArrays( GL_LINES, 0, m_uiControllerVertcount );
  		glBindVertexArray( 0 );
  	}
  
  	// ----- Render Model rendering -----
  	glUseProgram( m_unRenderModelProgramID );
  
  	for( uint32_t unTrackedDevice = 0; unTrackedDevice < vr::k_unMaxTrackedDeviceCount; unTrackedDevice++ )
  	{
  		if( !m_rTrackedDeviceToRenderModel[ unTrackedDevice ] || !m_rbShowTrackedDevice[ unTrackedDevice ] )
  			continue;
  
  		const vr::TrackedDevicePose_t & pose = m_rTrackedDevicePose[ unTrackedDevice ];
  		if( !pose.bPoseIsValid )
  			continue;
  
  		if( bIsInputCapturedByAnotherProcess && m_pHMD->GetTrackedDeviceClass( unTrackedDevice ) == vr::TrackedDeviceClass_Controller )
  			continue;
  
  		const Matrix4 & matDeviceToTracking = m_rmat4DevicePose[ unTrackedDevice ];
  		Matrix4 matMVP = GetCurrentViewProjectionMatrix( nEye ) * matDeviceToTracking;
  		glUniformMatrix4fv( m_nRenderModelMatrixLocation, 1, GL_FALSE, matMVP.get() );
  
  		m_rTrackedDeviceToRenderModel[ unTrackedDevice ]->Draw();
  	}
  
  	glUseProgram( 0 );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::RenderCompanionWindow()
  {
  	glDisable(GL_DEPTH_TEST);
  	glViewport( 0, 0, m_nCompanionWindowWidth, m_nCompanionWindowHeight );
  
  	glBindVertexArray( m_unCompanionWindowVAO );
  	glUseProgram( m_unCompanionWindowProgramID );
  
  	// render left eye (first half of index array )
  	glBindTexture(GL_TEXTURE_2D, leftEyeDesc.m_nResolveTextureId );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  	glDrawElements( GL_TRIANGLES, m_uiCompanionWindowIndexSize/2, GL_UNSIGNED_SHORT, 0 );
  
  	// render right eye (second half of index array )
  	glBindTexture(GL_TEXTURE_2D, rightEyeDesc.m_nResolveTextureId  );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
  	glDrawElements( GL_TRIANGLES, m_uiCompanionWindowIndexSize/2, GL_UNSIGNED_SHORT, (const void *)(uintptr_t)(m_uiCompanionWindowIndexSize) );
  
  	glBindVertexArray( 0 );
  	glUseProgram( 0 );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Gets a Matrix Projection Eye with respect to nEye.
  //-----------------------------------------------------------------------------
  Matrix4 CMainApplication::GetHMDMatrixProjectionEye( vr::Hmd_Eye nEye )
  {
  	if ( !m_pHMD )
  		return Matrix4();
  
  	vr::HmdMatrix44_t mat = m_pHMD->GetProjectionMatrix( nEye, m_fNearClip, m_fFarClip );
  
  	return Matrix4(
  		mat.m[0][0], mat.m[1][0], mat.m[2][0], mat.m[3][0],
  		mat.m[0][1], mat.m[1][1], mat.m[2][1], mat.m[3][1], 
  		mat.m[0][2], mat.m[1][2], mat.m[2][2], mat.m[3][2], 
  		mat.m[0][3], mat.m[1][3], mat.m[2][3], mat.m[3][3]
  	);
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Gets an HMDMatrixPoseEye with respect to nEye.
  //-----------------------------------------------------------------------------
  Matrix4 CMainApplication::GetHMDMatrixPoseEye( vr::Hmd_Eye nEye )
  {
  	if ( !m_pHMD )
  		return Matrix4();
  
  	vr::HmdMatrix34_t matEyeRight = m_pHMD->GetEyeToHeadTransform( nEye );
  	Matrix4 matrixObj(
  		matEyeRight.m[0][0], matEyeRight.m[1][0], matEyeRight.m[2][0], 0.0, 
  		matEyeRight.m[0][1], matEyeRight.m[1][1], matEyeRight.m[2][1], 0.0,
  		matEyeRight.m[0][2], matEyeRight.m[1][2], matEyeRight.m[2][2], 0.0,
  		matEyeRight.m[0][3], matEyeRight.m[1][3], matEyeRight.m[2][3], 1.0f
  		);
  
  	return matrixObj.invert();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Gets a Current View Projection Matrix with respect to nEye,
  //          which may be an Eye_Left or an Eye_Right.
  //-----------------------------------------------------------------------------
  Matrix4 CMainApplication::GetCurrentViewProjectionMatrix( vr::Hmd_Eye nEye )
  {
  	Matrix4 matMVP;
  	if( nEye == vr::Eye_Left )
  	{
  		matMVP = m_mat4ProjectionLeft * m_mat4eyePosLeft * m_mat4HMDPose;
  	}
  	else if( nEye == vr::Eye_Right )
  	{
  		matMVP = m_mat4ProjectionRight * m_mat4eyePosRight *  m_mat4HMDPose;
  	}
  
  	return matMVP;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  void CMainApplication::UpdateHMDMatrixPose()
  {
  	if ( !m_pHMD )
  		return;
  
  	vr::VRCompositor()->WaitGetPoses(m_rTrackedDevicePose, vr::k_unMaxTrackedDeviceCount, NULL, 0 );
  
  	m_iValidPoseCount = 0;
  	m_strPoseClasses = "";
  	for ( int nDevice = 0; nDevice < vr::k_unMaxTrackedDeviceCount; ++nDevice )
  	{
  		if ( m_rTrackedDevicePose[nDevice].bPoseIsValid )
  		{
  			m_iValidPoseCount++;
  			m_rmat4DevicePose[nDevice] = ConvertSteamVRMatrixToMatrix4( m_rTrackedDevicePose[nDevice].mDeviceToAbsoluteTracking );
  			if (m_rDevClassChar[nDevice]==0)
  			{
  				switch (m_pHMD->GetTrackedDeviceClass(nDevice))
  				{
  				case vr::TrackedDeviceClass_Controller:        m_rDevClassChar[nDevice] = 'C'; break;
  				case vr::TrackedDeviceClass_HMD:               m_rDevClassChar[nDevice] = 'H'; break;
  				case vr::TrackedDeviceClass_Invalid:           m_rDevClassChar[nDevice] = 'I'; break;
  				case vr::TrackedDeviceClass_GenericTracker:    m_rDevClassChar[nDevice] = 'G'; break;
  				case vr::TrackedDeviceClass_TrackingReference: m_rDevClassChar[nDevice] = 'T'; break;
  				default:                                       m_rDevClassChar[nDevice] = '?'; break;
  				}
  			}
  			m_strPoseClasses += m_rDevClassChar[nDevice];
  		}
  	}
  
  	if ( m_rTrackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid )
  	{
  		m_mat4HMDPose = m_rmat4DevicePose[vr::k_unTrackedDeviceIndex_Hmd];
  		m_mat4HMDPose.invert();
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Finds a render model we've already loaded or loads a new one
  //-----------------------------------------------------------------------------
  CGLRenderModel *CMainApplication::FindOrLoadRenderModel( const char *pchRenderModelName )
  {
  	CGLRenderModel *pRenderModel = NULL;
  	for( std::vector< CGLRenderModel * >::iterator i = m_vecRenderModels.begin(); i != m_vecRenderModels.end(); i++ )
  	{
  		if( !stricmp( (*i)->GetName().c_str(), pchRenderModelName ) )
  		{
  			pRenderModel = *i;
  			break;
  		}
  	}
  
  	// load the model if we didn't find one
  	if( !pRenderModel )
  	{
  		vr::RenderModel_t *pModel;
  		vr::EVRRenderModelError error;
  		while ( 1 )
  		{
  			error = vr::VRRenderModels()->LoadRenderModel_Async( pchRenderModelName, &pModel );
  			if ( error != vr::VRRenderModelError_Loading )
  				break;
  
  			ThreadSleep( 1 );
  		}
  
  		if ( error != vr::VRRenderModelError_None )
  		{
  			dprintf( "Unable to load render model %s - %s\n", pchRenderModelName, vr::VRRenderModels()->GetRenderModelErrorNameFromEnum( error ) );
  			return NULL; // move on to the next tracked device
  		}
  
  		vr::RenderModel_TextureMap_t *pTexture;
  		while ( 1 )
  		{
  			error = vr::VRRenderModels()->LoadTexture_Async( pModel->diffuseTextureId, &pTexture );
  			if ( error != vr::VRRenderModelError_Loading )
  				break;
  
  			ThreadSleep( 1 );
  		}
  
  		if ( error != vr::VRRenderModelError_None )
  		{
  			dprintf( "Unable to load render texture id:%d for render model %s\n", pModel->diffuseTextureId, pchRenderModelName );
  			vr::VRRenderModels()->FreeRenderModel( pModel );
  			return NULL; // move on to the next tracked device
  		}
  
  		pRenderModel = new CGLRenderModel( pchRenderModelName );
  		if ( !pRenderModel->BInit( *pModel, *pTexture ) )
  		{
  			dprintf( "Unable to create GL model from render model %s\n", pchRenderModelName );
  			delete pRenderModel;
  			pRenderModel = NULL;
  		}
  		else
  		{
  			m_vecRenderModels.push_back( pRenderModel );
  		}
  		vr::VRRenderModels()->FreeRenderModel( pModel );
  		vr::VRRenderModels()->FreeTexture( pTexture );
  	}
  	return pRenderModel;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Create/destroy GL a Render Model for a single tracked device
  //-----------------------------------------------------------------------------
  void CMainApplication::SetupRenderModelForTrackedDevice( vr::TrackedDeviceIndex_t unTrackedDeviceIndex )
  {
  	if( unTrackedDeviceIndex >= vr::k_unMaxTrackedDeviceCount )
  		return;
  
  	// try to find a model we've already set up
  	std::string sRenderModelName = GetTrackedDeviceString( m_pHMD, unTrackedDeviceIndex, vr::Prop_RenderModelName_String );
  	CGLRenderModel *pRenderModel = FindOrLoadRenderModel( sRenderModelName.c_str() );
  	if( !pRenderModel )
  	{
  		std::string sTrackingSystemName = GetTrackedDeviceString( m_pHMD, unTrackedDeviceIndex, vr::Prop_TrackingSystemName_String );
  		dprintf( "Unable to load render model for tracked device %d (%s.%s)", unTrackedDeviceIndex, sTrackingSystemName.c_str(), sRenderModelName.c_str() );
  	}
  	else
  	{
  		m_rTrackedDeviceToRenderModel[ unTrackedDeviceIndex ] = pRenderModel;
  		m_rbShowTrackedDevice[ unTrackedDeviceIndex ] = true;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Create/destroy GL Render Models
  //-----------------------------------------------------------------------------
  void CMainApplication::SetupRenderModels()
  {
  	memset( m_rTrackedDeviceToRenderModel, 0, sizeof( m_rTrackedDeviceToRenderModel ) );
  
  	if( !m_pHMD )
  		return;
  
  	for( uint32_t unTrackedDevice = vr::k_unTrackedDeviceIndex_Hmd + 1; unTrackedDevice < vr::k_unMaxTrackedDeviceCount; unTrackedDevice++ )
  	{
  		if( !m_pHMD->IsTrackedDeviceConnected( unTrackedDevice ) )
  			continue;
  
  		SetupRenderModelForTrackedDevice( unTrackedDevice );
  	}
  
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Converts a SteamVR matrix to our local matrix class
  //-----------------------------------------------------------------------------
  Matrix4 CMainApplication::ConvertSteamVRMatrixToMatrix4( const vr::HmdMatrix34_t &matPose )
  {
  	Matrix4 matrixObj(
  		matPose.m[0][0], matPose.m[1][0], matPose.m[2][0], 0.0,
  		matPose.m[0][1], matPose.m[1][1], matPose.m[2][1], 0.0,
  		matPose.m[0][2], matPose.m[1][2], matPose.m[2][2], 0.0,
  		matPose.m[0][3], matPose.m[1][3], matPose.m[2][3], 1.0f
  		);
  	return matrixObj;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Create/destroy GL Render Models
  //-----------------------------------------------------------------------------
  CGLRenderModel::CGLRenderModel( const std::string & sRenderModelName )
  	: m_sModelName( sRenderModelName )
  {
  	m_glIndexBuffer = 0;
  	m_glVertArray = 0;
  	m_glVertBuffer = 0;
  	m_glTexture = 0;
  }
  
  
  CGLRenderModel::~CGLRenderModel()
  {
  	Cleanup();
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Allocates and populates the GL resources for a render model
  //-----------------------------------------------------------------------------
  bool CGLRenderModel::BInit( const vr::RenderModel_t & vrModel, const vr::RenderModel_TextureMap_t & vrDiffuseTexture )
  {
  	// create and bind a VAO to hold state for this model
  	glGenVertexArrays( 1, &m_glVertArray );
  	glBindVertexArray( m_glVertArray );
  
  	// Populate a vertex buffer
  	glGenBuffers( 1, &m_glVertBuffer );
  	glBindBuffer( GL_ARRAY_BUFFER, m_glVertBuffer );
  	glBufferData( GL_ARRAY_BUFFER, sizeof( vr::RenderModel_Vertex_t ) * vrModel.unVertexCount, vrModel.rVertexData, GL_STATIC_DRAW );
  
  	// Identify the components in the vertex buffer
  	glEnableVertexAttribArray( 0 );
  	glVertexAttribPointer( 0, 3, GL_FLOAT, GL_FALSE, sizeof( vr::RenderModel_Vertex_t ), (void *)offsetof( vr::RenderModel_Vertex_t, vPosition ) );
  	glEnableVertexAttribArray( 1 );
  	glVertexAttribPointer( 1, 3, GL_FLOAT, GL_FALSE, sizeof( vr::RenderModel_Vertex_t ), (void *)offsetof( vr::RenderModel_Vertex_t, vNormal ) );
  	glEnableVertexAttribArray( 2 );
  	glVertexAttribPointer( 2, 2, GL_FLOAT, GL_FALSE, sizeof( vr::RenderModel_Vertex_t ), (void *)offsetof( vr::RenderModel_Vertex_t, rfTextureCoord ) );
  
  	// Create and populate the index buffer
  	glGenBuffers( 1, &m_glIndexBuffer );
  	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_glIndexBuffer );
  	glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( uint16_t ) * vrModel.unTriangleCount * 3, vrModel.rIndexData, GL_STATIC_DRAW );
  
  	glBindVertexArray( 0 );
  
  	// create and populate the texture
  	glGenTextures(1, &m_glTexture );
  	glBindTexture( GL_TEXTURE_2D, m_glTexture );
  
  	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, vrDiffuseTexture.unWidth, vrDiffuseTexture.unHeight,
  		0, GL_RGBA, GL_UNSIGNED_BYTE, vrDiffuseTexture.rubTextureMapData );
  
  	// If this renders black ask McJohn what's wrong.
  	glGenerateMipmap(GL_TEXTURE_2D);
  
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
  	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
  
  	GLfloat fLargest;
  	glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest );
  	glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest );
  
  	glBindTexture( GL_TEXTURE_2D, 0 );
  
  	m_unVertexCount = vrModel.unTriangleCount * 3;
  
  	return true;
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Frees the GL resources for a render model
  //-----------------------------------------------------------------------------
  void CGLRenderModel::Cleanup()
  {
  	if( m_glVertBuffer )
  	{
  		glDeleteBuffers(1, &m_glIndexBuffer);
  		glDeleteVertexArrays( 1, &m_glVertArray );
  		glDeleteBuffers(1, &m_glVertBuffer);
  		m_glIndexBuffer = 0;
  		m_glVertArray = 0;
  		m_glVertBuffer = 0;
  	}
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose: Draws the render model
  //-----------------------------------------------------------------------------
  void CGLRenderModel::Draw()
  {
  	glBindVertexArray( m_glVertArray );
  
  	glActiveTexture( GL_TEXTURE0 );
  	glBindTexture( GL_TEXTURE_2D, m_glTexture );
  
  	glDrawElements( GL_TRIANGLES, m_unVertexCount, GL_UNSIGNED_SHORT, 0 );
  
  	glBindVertexArray( 0 );
  }
  
  
  //-----------------------------------------------------------------------------
  // Purpose:
  //-----------------------------------------------------------------------------
  int main(int argc, char *argv[])
  {
  	CMainApplication *pMainApplication = new CMainApplication( argc, argv );
  
  	if (!pMainApplication->BInit())
  	{
  		pMainApplication->Shutdown();
  		return 1;
  	}
  
  	pMainApplication->RunMainLoop();
  
  	pMainApplication->Shutdown();
  
  	return 0;
  }
  