Head Tracking

From Valve Developer Community
Jump to navigation Jump to search

Head Tracking

This tutorial shows how to interface an external 6 DOF tracking or 6 DOF input device to your Valve Source SDK Mod so you can control the player with the device instead of the mouse and keyboard.

Note, for many 3 DOF (orientation only) input devices (e.g., trackball) you can simply use your device's driver to masquerade as a mouse. But with 6 DOF devices (position and orientation) you have to something more. This tutorial supports external tracking devices (6DOF or less) that can't masquerade as a mouse and possible involve position and orientation

Step 1: Create an Interface to work with Any Tracker

The first step is to create a helper interface that will be used to access your tracker. This class will have interface methods that Valve will call when it needs position and orientation information. This is a software engineering drill, but will be very useful when you want to use more than one type of tracker.


Step 1A: Create the file CPPInterfaces2.h

Put this code inside:

//
// CppInterfaces2.h
//

#define Interface class

#define implements public

#define DeclareInterface(name) __interface actual_##name {

#define DeclareBasedInterface(name, base) __interface actual_##name \
     : public actual_##base {

#define EndInterface(name) };                \
     Interface name : public actual_##name { \
     public:                                 \
        virtual ~name() {}                   \
     };

This is just a bunch of compiler macros that will enforce interface constraints on your code. C++ doesn't provide native interface objects, so this is a way to enforce it so yuo can have the idea of an interface object. If you want the full details, check out this article by Jose Rios

Step 1B: Create the file cl_dll\IMovementController.h.

Put this code inside:


//Include interface enforement directives
#include "CPPInterfaces2.h"

/****************************************************
*  This interface is used to integrate all 
*  Valve movement controllers
*
*
*  Each movement controller provides 6-DOF on
*  a particular object (body part, other tracked object
*
*  This interface assumes that any implementing class
*  will perform any required post processing to transform
*  tracking results into a right handed coordinate system
*  with +Z up -- with units in inches.
*
*****************************************************/
DeclareInterface(IMovementController)
	/**
	*  Returns the orientation from the tracker.  Assumes angles are relative to a right handed coord system with +Z up.
	*  Assumes update() has been called.
	*/
	int		getOrientation(float &pitch, float &yaw, float &roll);

	/**
	*  Returns the position from the tracker.  Assumes coordinates are relative to a right handed coord system with +Z up.
	*  Assumes update() has been called.
	*/
	int		getPosition(float &x, float &y, float &z);
	
        /**
        * Returns true if the tracker is initialized and ready to track
        */
	bool	        isTrackerInitialized();

	/**
	*  Reads the hardware and updates local internal state variables for later read by accessors.
	*/
	void	update();

        /**
        *  Returns true if the tracker has good position info
        */
	bool	hasPositionTracking();

        /**
        *  Returns true if the tracker has good/reliable orientation info
        */
	bool	hasOrientationTracking();
EndInterface(IMovementController)

Again, this is just an interface and only ensures that when you implement a tracker, you obey a set of rules that will allow you to swap out trackers at compile time very easily.

Step 2: Create an Instance of the Interface for Your Particular Tracker

The class will implement the interface in Step 1, but have tracker specific API calls and code (the "guts") that deal with the nuances of your particular tracker SDK (e.g., FACE API, Intersense API, etc).

Step 2A : Create your Instance

Header File

Create a file cl_dll\MyMovementController.h. Place this inside:


#include "IMovementController.h" // The movement control interface
#include /path/to/your/tracker/api


class MyMovementController : implements IMovementController {

/************************** Member Functions **************************/
public:
	
	/**
	*  Construct a new ar_movement_controller
	*/
	MyMovementController();

	/**
	*  Destructor  Closes tracker and performs clean up
	*/
	~MyMovementController();

		
  /**	
  * Returns the pitch, yaw, and roll Euler angles of the tracker
  */
	int		getOrientation(float &pitch, float &yaw, float &roll);
	
	/**
	* Returns the position (in inches) of the tracker
	*/
	int		getPosition(float &x, float &y, float &z);
	
	/** 
	* Returns true if the tracker has reliable position information
	*/
	bool	hasPositionTracking(void);
	
	/**
	* Returns true if the tracker has reliable orientation information
	*/
	bool	hasOrientationTracking(void);
	
	/**
	* Returns true if the tracker is alive/ready
	*/
	bool	isTrackerInitialized(void);
	
	/**
	* Tells the tracker that its time/safe to update.
	*/
	void	update(void);

	
};


Class File

Create a file cl_dll\MyMovementController.cpp. Place this inside:

/****************************************************************************
*  Sample shell for specific tracker instance
*
*  This is very dependent on what tracker and API you are using
* 
*  This example is a conceptual template of how it would look
*
******************************************************************************/
#include "cbase.h"
#include "MyMovementController.h"

/****************************************************************************
*  Constructor 
******************************************************************************/
MyMovementController::MyMovementController() {

	//Put in code here that is needed to connect to your tracker and initialize it
	my_tracking_api.open();

}

/****************************************************************************
*  Destructor
******************************************************************************/
MyMovementController::~MyMovementController(){

	//Put in code here that shuts down the tracker
	my_tracking_api.close();
}


/****************************************************************************
*
*   functionName:   GetOrientation
*   Description:    Get the current orientation from the tracker
*
******************************************************************************/
int MyMovementController::getOrientation(float &pitch, float &yaw, float &roll){

	//Here just set pitch, roll, yaw using your tracker API/SDK
	pitch = my_tracking_api.getPitch();
	yaw = my_tracking_api.getYaw();
	roll = my_tracking_api.getRoll();

	//Note:  If you need to do any rotations, say to adding 90 degrees to turn the tracker to face
	// 			 the player, then this is a good place

	return 0;
}


/****************************************************************************
*
*   functionName:   GetPosition
*   Description:    Get the current position from the tracker
*
******************************************************************************/
int MyMovementController::getPosition(float &x, float &y, float &z){

	//Here just set x, y, and z using your tracker API/SDK
	x = my_tracking_api.getX_Pos();
	y = my_tracking_api.getY_Pos();
	z = my_tracking_api.getZ_Pos();

	//Note:  If you need to add offsets or convert units (meter's the inches, etc) then you can
	//				do that here

	return 0;
}

/****************************************************************************
*  Cues the tracker for an update
*
******************************************************************************/
void MyMovementController::update() {
	
		//Here use your tracker API/SDK to perform any per-frame steps that must
		//be called to update the tracker
		my_tracking_api.update();
}

/****************************************************************************
*
*  Checkes if the tracker is alive
*
******************************************************************************/
bool MyMovementController::isTrackerInitialized() {
	
		//Here use your tracker API/SDK to check the status of the tracker
		return my_tracking_api.trackerReady();
}

/****************************************************************************
*
*  Checkes if the tracker has valid orientation data
*
******************************************************************************/
bool MyMovementController::hasOrientationTracking() {
	
		//Here use your tracker API/SDK to check the orientation of the tracker
		return my_tracking_api.hasOrientationData();
}


/****************************************************************************
*
*  Checkes if the tracker has valid orientation data
*
******************************************************************************/
bool MyMovementController::hasPositionTracking() {
	
		//Here use your tracker API/SDK to check the position tracking of the tracker
		return my_tracking_api.hasPositionData();
}

Step 3 : Modify Valve's in_main.cpp File

Modify cl_dll\in_main.cpp as follows.


In the top of the file, add a reference to your tracker:

//The movement controller used for head tracking
IMovementController* arctl_head;

Find the method void IN_CenterView_f (void). Change it to this:

void IN_CenterView_f (void)
{
        //Use mouse if tracker is unavailable
	if(!arctl_head->isTrackerInitialized() || (useTracking == false)){ 
		QAngle viewangles;
		
		//arctl_head->report();

		if ( UsingMouselook() == false )
		{
			if ( !::input->CAM_InterceptingMouse() )
			{
				engine->GetViewAngles( viewangles );
				viewangles[PITCH] = 0;
				engine->SetViewAngles( viewangles );
				//prediction->SetLocalViewAngles( viewangles );  //STEVE ALBANY

			}
		}
	}
}

This allows you to use the mouse when the tracker isn't active.

Find the method void CInput::AdjustAngles ( float frametime ). Change it to this:

void CInput::AdjustAngles ( float frametime )
{
	float	speed;
	QAngle viewangles;
	
	// Determine control scaling factor ( multiplies time )
	speed = DetermineKeySpeed( frametime );

	// Retrieve latest view direction from engine
	engine->GetViewAngles( viewangles );

        //Get view angle from the tracker
	if(arctl_head->isTrackerInitialized() && (useTracking == true)){
		
		//arctl_head->report();

		//Only update once and awhile -- we are flooding the pipe 
		arctl_update_count++;
		if(arctl_update_count > ARCTL_UPDATE_INVERVAL) {
			arctl_update_count=0;
			arctl_head->update();	
		}		
		

		viewangles = getCameraAngles();
		//Msg("myang = %f, %f, %f\n", myAng.x, myAng.y, myAng.z);

		//NEED TO GET THE CAMERA ANGLES FROM THE TRACKER

	}
	else{
		//Msg("in_main 1547..tracker not enabled...\n");
		// Adjust YAW
		AdjustYaw( speed, viewangles );

		// Adjust PITCH if keyboard looking
		AdjustPitch( speed, viewangles );

		// Make sure values are legitimate
		ClampAngles( viewangles );
	}


	// Store new view angles into engine view direction
	engine->SetViewAngles( viewangles );

}


Find the method void CInput::ControllerMove( float frametime, CUserCmd *cmd ). Change it to this:

void CInput::ControllerMove( float frametime, CUserCmd *cmd )
{
	if(!arctl_head->isTrackerInitialized() || (useTracking == false)){ //OHAN ADD - Use mouse if tracker is unavailable //STEVE or disabled
		if ( IsPC() )
		{
			if ( !m_fCameraInterceptingMouse && m_fMouseActive )
			{
				MouseMove( cmd);
			}
		}
		//arctl_head->report();
		JoyStickMove( frametime, cmd);
	}
}

Find the method void CInput::ExtraMouseSample( float frametime, bool active ). Change it to this:

void CInput::ExtraMouseSample( float frametime, bool active )
{
	if(!arctl_head->isTrackerInitialized() || (useTracking == false)){ //OHAN ADD - Use mouse if tracker is unavailable //STEVE -- or disabled
		CUserCmd dummy;
		CUserCmd *cmd = &dummy;

		cmd->Reset();
		//arctl_head->report();

		QAngle viewangles;

		if ( active )
		{
			// Determine view angles
			AdjustAngles ( frametime );

			// Determine sideways movement
			ComputeSideMove( cmd );

			// Determine vertical movement
			ComputeUpwardMove( cmd );

			// Determine forward movement
			ComputeForwardMove( cmd );

			// Scale based on holding speed key or having too fast of a velocity based on client maximum
			//  speed.
			ScaleMovements( cmd );

			// Allow mice and other controllers to add their inputs
			ControllerMove( frametime, cmd );
		}

		// Retreive view angles from engine ( could have been set in IN_AdjustAngles above )
		engine->GetViewAngles( viewangles );

		// Set button and flag bits, don't blow away state
		cmd->buttons = GetButtonBits( 0 );

		// Use new view angles if alive, otherwise user last angles we stored off.
		if ( g_iAlive )
		{
			VectorCopy( viewangles, cmd->viewangles );
			VectorCopy( viewangles, m_angPreviousViewAngles );
		}
		else
		{
			VectorCopy( m_angPreviousViewAngles, cmd->viewangles );
		}

		// Let the move manager override anything it wants to.
		g_pClientMode->CreateMove( frametime, cmd );

		// Get current view angles after the client mode tweaks with it
		engine->SetViewAngles( cmd->viewangles );

		prediction->SetLocalViewAngles( cmd->viewangles );
	}
}

Step 4 : Modify Valve's Server Notion of Player Position