/********************************************************************************
* FaceTrackNoIR This program is a private project of the some enthusiastic *
* gamers from Holland, who don't like to pay for *
* head-tracking. *
* *
* Copyright (C) 2010 Wim Vriend (Developing) *
* Ron Hendriks (Researching and Testing) *
* *
* Homepage *
* *
* This program is free software; you can redistribute it and/or modify it *
* under the terms of the GNU General Public License as published by the *
* Free Software Foundation; either version 3 of the License, or (at your *
* option) any later version. *
* *
* This program is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY *
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for *
* more details. *
* *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, see . *
*********************************************************************************/
/*
Modifications (last one on top):
20100517 - WVR: Added upstream command(s) from FlightGear
20100523 - WVR: Checkboxes to invert 6DOF's was implemented. Multiply by
1 or (-1).
*/
#include "tracker.h"
#include "FaceTrackNoIR.h"
void initDInput(HINSTANCE hInstance, HWND hWnd); // sets up and initializes DirectInput
void detect_input(void); // gets the current input state
void cleanDInput(void); // closes DirectInput and releases memory
using namespace sm::faceapi;
using namespace sm::faceapi::qt;
float Tracker::headPosX = 0.0f;
float Tracker::headPosY = 0.0f;
float Tracker::headPosZ = 0.0f;
float Tracker::initial_headPosZ = 0.0f;
float Tracker::headRotX = 0.0f;
float Tracker::headRotY = 0.0f;
float Tracker::headRotZ = 0.0f;
bool Tracker::confid = false;
bool Tracker::set_initial = false;
float Tracker::sensYaw = 1.0f;
float Tracker::sensPitch = 1.0f;
float Tracker::sensRoll = 1.0f;
float Tracker::sensX = 1.0f;
float Tracker::sensY = 1.0f;
float Tracker::sensZ = 1.0f;
float Tracker::invertYaw = 1.0f;
float Tracker::invertPitch = 1.0f;
float Tracker::invertRoll = 1.0f;
float Tracker::invertX = 1.0f;
float Tracker::invertY = 1.0f;
float Tracker::invertZ = 1.0f;
float Tracker::rotNeutralZone = 0.087f; // Neutral Zone for rotations (rad)
/** constructor empty **/
Tracker::Tracker() {
// Create events
m_StopThread = CreateEvent(0, TRUE, FALSE, 0);
m_WaitThread = CreateEvent(0, TRUE, FALSE, 0);
try {
// Initialize the faceAPI Qt library
sm::faceapi::qt::initialize();
smLoggingSetFileOutputEnable( false );
// Initialize the API
faceapi_scope = new APIScope;
// Create head-tracking engine v2 using first detected webcam
CameraInfo::registerType(SM_API_CAMERA_TYPE_WDM);
_engine = QSharedPointer(new HeadTrackerV2());
// starts the faceapi engine
_engine->start();
}
catch (sm::faceapi::Error &e)
{
/* ERROR with camera */
QMessageBox::warning(0,"faceAPI Error",e.what(),QMessageBox::Ok,QMessageBox::NoButton);
}
server_FT = new FTServer; // Create the new thread (on the heap)
server_FG = new FGServer ( this ); // Create the new thread (on the heap)
}
/** destructor empty **/
Tracker::~Tracker() {
server_FT->deleteLater();
server_FG->deleteLater();
// Trigger thread to stop
::SetEvent(m_StopThread);
// Wait until thread finished
::WaitForSingleObject(m_WaitThread, INFINITE);
// Close handles
::CloseHandle(m_StopThread);
::CloseHandle(m_WaitThread);
_engine->stop();
smAPIQuit();
}
/** setting up the tracker engine **/
void Tracker::setup(QWidget *head, FaceTrackNoIR *parent) {
bool DLL_Ok;
// retrieve pointers to the User Interface and the main Application
headPoseWidget = head;
mainApp = parent;
//registers the faceapi callback for receiving headpose data **/
registerHeadPoseCallback();
// some parameteres [optional]
smHTSetHeadPosePredictionEnabled( _engine->handle(), true);
smHTSetLipTrackingEnabled( _engine->handle(), false);
smLoggingSetFileOutputEnable( false );
// set up the line edits for calling
headXLine = headPoseWidget->findChild("headXLine");
headYLine = headPoseWidget->findChild("headYLine");
headZLine = headPoseWidget->findChild("headZLine");
headRotXLine = headPoseWidget->findChild("headRotXLine");
headRotYLine = headPoseWidget->findChild("headRotYLine");
headRotZLine = headPoseWidget->findChild("headRotZLine");
//
// Check if the Freetrack Client DLL is available
// and create the necessary mapping to shared memory.
// The handle of the MainWindow is sent to 'The Game', so it can send a message back.
//
DLL_Ok = server_FT->FTCheckClientDLL();
DLL_Ok = server_FT->FTCreateMapping( mainApp->winId() );
qDebug() << "FaceTrackNoIR says: Window Handle =" << mainApp->winId();
// return;
server_FT->start(); // Should start at the push of a button?
server_FG->start(); //
}
/** QThread run method @override **/
void Tracker::run() {
/** Direct Input variables **/
LPDIRECTINPUT8 din; // the pointer to our DirectInput interface
LPDIRECTINPUTDEVICE8 dinkeyboard; // the pointer to the keyboard device
BYTE keystate[256]; // the storage for the key-information
HRESULT retAcquire;
//
// Setup the DirectInput for keyboard strokes
//
// create the DirectInput interface
if (DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8,
(void**)&din, NULL) != DI_OK) { // COM stuff, so we'll set it to NULL
qDebug() << "Tracker::setup DirectInput8 Creation failed!" << GetLastError();
}
else {
qDebug() << "Tracker::setup DirectInput8 Creation OK!";
}
// create the keyboard device
if (din->CreateDevice(GUID_SysKeyboard, &dinkeyboard, NULL) != DI_OK) {
qDebug() << "Tracker::setup CreateDevice function failed!" << GetLastError();
}
else {
qDebug() << "Tracker::setup CreateDevice function OK!";
}
// set the data format to keyboard format
if (dinkeyboard->SetDataFormat(&c_dfDIKeyboard) != DI_OK) {
qDebug() << "Tracker::setup SetDataFormat function failed!" << GetLastError();
}
else {
qDebug() << "Tracker::setup SetDataFormat function OK!";
}
// set the control you will have over the keyboard
if (dinkeyboard->SetCooperativeLevel(mainApp->winId(), DISCL_NONEXCLUSIVE | DISCL_BACKGROUND) != DI_OK) {
qDebug() << "Tracker::setup SetCooperativeLevel function failed!" << GetLastError();
}
else {
qDebug() << "Tracker::setup SetCooperativeLevel function OK!";
}
forever
{
// Check event for stop thread
if(::WaitForSingleObject(m_StopThread, 0) == WAIT_OBJECT_0)
{
// Set event
::SetEvent(m_WaitThread);
return;
}
//
// Check the keyboard
//
// get access if we don't have it already
retAcquire = dinkeyboard->Acquire();
if ( (retAcquire != DI_OK) && (retAcquire != S_FALSE) ) {
qDebug() << "Tracker::run Acquire function failed!" << GetLastError();
}
else {
// get the input data
if (dinkeyboard->GetDeviceState(256, (LPVOID)keystate) != DI_OK) {
qDebug() << "Tracker::run GetDeviceState function failed!" << GetLastError();
}
else {
qDebug() << "Tracker::run GetDeviceState function OK!";
if(keystate[DIK_HOME] & 0x80) {
qDebug() << "Tracker::run() says HOME pressed";
}
}
}
//if the confidence is good enough the headpose will be updated **/
if(Tracker::confid) {
//
// Most games need an offset to the initial position and NOT the
// absolute distance to the camera: so remember the initial distance
// to substract that later...
//
if(Tracker::set_initial == false) {
Tracker::initial_headPosZ = Tracker::getHeadPosZ();
Tracker::set_initial = true;
}
headXLine->setText(QString("%1").arg(Tracker::getHeadPosX()*100, 0, 'f', 1));
headYLine->setText(QString("%1").arg(Tracker::getHeadPosY()*100, 0, 'f', 1));
headZLine->setText(QString("%1").arg(Tracker::getHeadPosZ()*100, 0, 'f', 1));
headRotXLine->setText(QString("%1").arg(Tracker::getHeadRotX()*100, 0, 'f', 1));
headRotYLine->setText(QString("%1").arg(Tracker::getHeadRotY()*100, 0, 'f', 1));
headRotZLine->setText(QString("%1").arg(Tracker::getHeadRotZ()*100, 0, 'f', 1));
//// listener.setTrackedPosition(QPoint(Tracker::getHeadPosX()-50, Tracker::getHeadPosY()-37.5));
server_FT->setHeadRotX( Tracker::headRotX ); // rads (?)
server_FT->setHeadRotY( Tracker::headRotY );
server_FT->setHeadRotZ( Tracker::headRotZ );
server_FT->setHeadPosX( Tracker::headPosX * 1000.0f); // From m to mm
server_FT->setHeadPosY( Tracker::headPosY * 1000.0f);
server_FT->setHeadPosZ( ( Tracker::headPosZ - Tracker::initial_headPosZ ) * 1000.0f);
//
// Add the raw values to the QList, so they can be smoothed.
// The raw value that enters the QList is first (evt.) inverted and corrected for Neutral Zone.
//
addRaw2List ( &rawPitchList, intMaxPitchItems, getCorrectedNewRaw ( Tracker::invertPitch * Tracker::headRotX , Tracker::rotNeutralZone ) );
addRaw2List ( &rawYawList, intMaxYawItems, getCorrectedNewRaw ( Tracker::invertYaw * Tracker::headRotY , Tracker::rotNeutralZone ) );
addRaw2List ( &rawRollList, intMaxRollItems, getCorrectedNewRaw ( Tracker::invertRoll * Tracker::headRotZ , Tracker::rotNeutralZone ) );
addRaw2List ( &rawXList, intMaxXItems, Tracker::invertX * Tracker::headPosX * 1000.0f );
addRaw2List ( &rawYList, intMaxYItems, Tracker::invertY * Tracker::headPosY * 1000.0f );
addRaw2List ( &rawZList, intMaxZItems, ( Tracker::invertZ * Tracker::headPosZ - Tracker::initial_headPosZ ) * 1000.0f );
}
//
// Also send the Virtual Pose to FT-server
//
server_FT->setVirtRotX( Tracker::sensPitch * getSmoothFromList( &rawPitchList ) );
server_FT->setVirtRotY( Tracker::sensYaw * getSmoothFromList( &rawYawList ) );
server_FT->setVirtRotZ( Tracker::sensRoll * getSmoothFromList( &rawRollList ) );
server_FT->setVirtPosX ( Tracker::sensX * getSmoothFromList( &rawXList ) );
server_FT->setVirtPosY ( Tracker::sensY * getSmoothFromList( &rawYList ) );
server_FT->setVirtPosZ ( Tracker::sensZ * getSmoothFromList( &rawZList ) );
server_FG->setVirtRotX( getDegreesFromRads ( Tracker::sensPitch * getSmoothFromList( &rawPitchList ) ) );
server_FG->setVirtRotY( getDegreesFromRads ( Tracker::sensYaw * getSmoothFromList( &rawYawList ) ) );
server_FG->setVirtRotZ( getDegreesFromRads ( Tracker::sensRoll * getSmoothFromList( &rawRollList ) ) );
server_FG->setVirtPosX ( ( Tracker::sensX * getSmoothFromList( &rawXList ) ) / 1000.0f );
server_FG->setVirtPosY ( ( Tracker::sensY * getSmoothFromList( &rawYList ) ) / 1000.0f );
server_FG->setVirtPosZ ( ( Tracker::sensZ * getSmoothFromList( &rawZList ) ) / 1000.0f );
//for lower cpu load
msleep(50);
yieldCurrentThread();
}
}
/** registers the faceapi headpose callback function **/
void Tracker::registerHeadPoseCallback() {
Q_ASSERT(_engine_handle);
smReturnCode error = smHTRegisterHeadPoseCallback( _engine->handle(), 0, receiveHeadPose);
//showErrorBox(0, "Register HeadPose Callback", error);
start(LowestPriority);
}
/** Callback function for head-pose - only static methods could be called **/
void Tracker::receiveHeadPose(void *,smEngineHeadPoseData head_pose, smCameraVideoFrame video_frame)
{
if(head_pose.confidence>0) {
Tracker::confid = true;
Tracker::setHeadPosX(head_pose.head_pos.x);
Tracker::setHeadPosY(head_pose.head_pos.y);
Tracker::setHeadPosZ(head_pose.head_pos.z);
Tracker::setHeadRotX(head_pose.head_rot.x_rads);
Tracker::setHeadRotY(head_pose.head_rot.y_rads);
Tracker::setHeadRotZ(head_pose.head_rot.z_rads);
} else {
Tracker::confid = false;
}
// for lower cpu load
msleep(50);
yieldCurrentThread();
}
//
// Get the ProgramName from the Game and return it.
//
QString Tracker::getGameProgramName() {
QString str;
if ( server_FT ) {
str = server_FT->FTGetProgramName();
}
return str;
}
//
// Handle the command, send upstream by the game.
// Valid values are:
// 1 = reset Headpose
//
bool Tracker::handleGameCommand ( int command ) {
qDebug() << "handleGameCommand says: Command =" << command;
switch ( command ) {
case 1: // reset headtracker
if ( _engine ) {
_engine->stop();
_engine->start();
}
break;
default:
break;
}
return false;
}
//
// Add the new Raw value to the QList.
// Remove the last item(s), depending on the set maximum list-items.
//
void Tracker::addRaw2List ( QList *rawList, float maxIndex, float raw ) {
//
// Remove old values from the end of the QList.
// If the setting for MaxItems was lowered, the QList is shortened here...
//
while (rawList->size() >= maxIndex) {
rawList->removeLast();
}
//
// Insert the newest at the beginning.
//
rawList->prepend ( raw );
}
//
// Get the Smoothed value from the QList.
//
float Tracker::getSmoothFromList ( QList *rawList ) {
float sum = 0;
if (rawList->isEmpty()) return 0.0f;
//
// Add the Raw values and divide.
//
for ( int i = 0; i < rawList->size(); i++) {
sum += rawList->at(i);
}
return sum / rawList->size();
}
//
// Correct the Raw value, with the Neutral Zone supplied
//
float Tracker::getCorrectedNewRaw ( float NewRaw, float rotNeutral ) {
//
// Return 0, if NewRaw is within the Neutral Zone
//
if ( fabs( NewRaw ) < rotNeutral ) {
return 0.0f;
}
//
// NewRaw is outside the zone.
// Substract rotNeutral from the NewRaw
//
if ( NewRaw > 0.0f ) {
return (NewRaw - rotNeutral);
}
else {
return (NewRaw + rotNeutral); // Makes sense?
}
}