Module: qdxjoy.cpp

class QDXJoy
.h

constructorQDXJoy(int _n)

Creates an interface to joystick device #n (n=index of attached controllers)

destructor~QDXJoy()

Flat functions
 

EnumCallbackBOOL CALLBACK EnumCallback(const DIDEVICEINSTANCE *pdidInstance,void *ctx)

Called when a joystick is found in QDXJoy ctor

qlogqlog(QLOG_INFO,"joy %p found",joy);

// Get an interface to the enumerated joystick
hr=qDXInput->GetPDI()->CreateDeviceEx(pdidInstance->guidInstance,
IID_IDirectInputDevice2,(void**)&dxJoyLocal,NULL);
// This may fail, for example, if the user just plugs out the controller
if(FAILED(hr))
return DIENUM_CONTINUE;
// Store in class
joy->_SetInputDevice(dxJoyLocal);
// We're done
return DIENUM_STOP;
}
BOOL CALLBACK EnumAxesCallback(const DIDEVICEOBJECTINSTANCE *pdidoi,void *ctx)
Called for every axis on the controller

qlogqlog(QLOG_INFO,"QDXJoy; axis %d, range %d-%d\n",pdidoi->dwOfs,diprg.lMin,diprg.lMax);

return DIENUM_CONTINUE;
}


class QDXJoy
.h

_SetInputDevicevoid _SetInputDevice(LPDIRECTINPUTDEVICE2 joy)

Used in creating the joystick device (called from EnumCallback)

IsOpenbool IsOpen()

Returns TRUE if a joystick device has been opened

Openbool Open()

Open the joystick


Flat functions
 

qlog(QLOG_INFO,"Joystick found, FF=%d\n",IsForceFeedbacqlog(QLOG_INFO,"Joystick found, FF=%d\n",IsForceFeedback());

class QDXJoy
.h

Closevoid Close()

Close the joystick device

Acquirebool Acquire()

Acquire the joystick for use

Unacquirebool Unacquire()

Unacquire the joystick; somebody else may have it

Pollvoid Poll()
IsForceFeedbackbool IsForceFeedback()

Returns TRUE if the joystick is capable of force feedback

DisableAutoCenterbool DisableAutoCenter()

Turn off auto-centering for joystick
Returns TRUE if succesful

EnableAutoCenterbool EnableAutoCenter()

Turn on auto-centering for joystick
Returns TRUE if succesful


class QDXFFEffect
.h

constructorQDXFFEffect(QDXJoy *joy)
destructor~QDXFFEffect()
Startvoid Start()
Stopvoid Stop()
Releasevoid Release()
SetupConstantForcebool SetupConstantForce()

Create an effect for a constant force. Returns TRUE if ok.

UpdateConstantForcebool UpdateConstantForce(int fx,int fy)

Update the constant force effect



/*
 * QDXJoy - a DirectX joystick
 * 04-09-00: Created! (Win32) (heavily based on the JoyStImm MS SDK sample)
 * NOTES:
 * - Perhaps QJoystick would be a better name, but this is tied to DirectX
 * and a more general joystick class might be needed above this one.
 * - Only attached joysticks will be found
 * BUGS:
 * - Always finds and uses FIRST joystick, instead of searching for #n
 * - Expects a QSHELL to exist (for exclusive access when our window is current)
 * (C) MarketGraph/Ruud van Gaal
 */

#include <qlib/dxjoy.h>
#include <qlib/app.h>
#include <math.h>
#include <qlib/debug.h>
DEBUG_ENABLE

/************
* JOYSTICKS *
************/
QDXJoy::QDXJoy(int _n)
// Creates an interface to joystick device #n (n=index of attached controllers)
{
  int i;
qdbg("QDXJoy ctor\n");

  x=y=z=0;
  rx=ry=rz=0;
  for(i=0;i<MAX_BUTTON;i++)
    button[i]=0;

#ifdef WIN32
  dxJoy=0;
  memset(&diDevCaps,0,sizeof(diDevCaps));
  n=_n;

  // Make sure DirectInput is up
  QDXINPUT_OPEN;
  if(!qDXInput->IsOpen())return;

  Open();
#endif
}
QDXJoy::~QDXJoy()
{
  Close();
}

#ifdef WIN32
BOOL CALLBACK EnumCallback(const DIDEVICEINSTANCE *pdidInstance,void *ctx)
// Called when a joystick is found in QDXJoy ctor
{
  QDXJoy *joy;
  HRESULT hr;
  LPDIRECTINPUTDEVICE2 dxJoyLocal;

  joy=(QDXJoy*)ctx;
qlog(QLOG_INFO,"joy %p found",joy);
  // Get an interface to the enumerated joystick
  hr=qDXInput->GetPDI()->CreateDeviceEx(pdidInstance->guidInstance,
    IID_IDirectInputDevice2,(void**)&dxJoyLocal,NULL);
  // This may fail, for example, if the user just plugs out the controller
  if(FAILED(hr))
    return DIENUM_CONTINUE;
  // Store in class
  joy->_SetInputDevice(dxJoyLocal);
  // We're done
  return DIENUM_STOP;
}
BOOL CALLBACK EnumAxesCallback(const DIDEVICEOBJECTINSTANCE *pdidoi,void *ctx)
// Called for every axis on the controller
{
  QDXJoy *joy;

  joy=(QDXJoy*)ctx;

  DIPROPRANGE diprg;
  diprg.diph.dwSize=sizeof(DIPROPRANGE);
  diprg.diph.dwHeaderSize=sizeof(DIPROPHEADER);
  diprg.diph.dwHow=DIPH_BYOFFSET;
  diprg.diph.dwObj=pdidoi->dwOfs;   // Specify the enumerated axis
  diprg.lMin=-1000;
  diprg.lMax=1000;

  // Set range for the axis
  if(FAILED(joy->GetDXJoy()->SetProperty(DIPROP_RANGE,&diprg.diph)))
  { qwarn("QDXJoy: Can't set axis range (axis=%d)",pdidoi->dwOfs);
    return DIENUM_STOP;
  }
  if(FAILED(joy->GetDXJoy()->GetProperty(DIPROP_RANGE,&diprg.diph)))
  {
    qwarn("QDXJoy: Can't get axis range (axis=%d)",pdidoi->dwOfs);
    return DIENUM_STOP;
  }
qlog(QLOG_INFO,"QDXJoy; axis %d, range %d-%d\n",pdidoi->dwOfs,diprg.lMin,diprg.lMax);
  return DIENUM_CONTINUE;
}

void QDXJoy::_SetInputDevice(LPDIRECTINPUTDEVICE2 joy)
// Used in creating the joystick device (called from EnumCallback)
{
  dxJoy=joy;
}
#endif

/**********
* ATTRIBS *
**********/
bool QDXJoy::IsOpen()
// Returns TRUE if a joystick device has been opened
{
#ifdef WIN32
  if(dxJoy)return TRUE;
#endif
  return FALSE;
}

bool QDXJoy::Open()
// Open the joystick
{
#ifdef WIN32
  // Look for a joystick (that is currently attached)
  HRESULT hr;
  hr=qDXInput->GetPDI()->EnumDevices(DIDEVTYPE_JOYSTICK,EnumCallback,this,
    DIEDFL_ATTACHEDONLY /*|DIEDFL_FORCEFEEDBACK*/);
  if(FAILED(hr)||dxJoy==0)
  { qwarn("QDXJoy: can't create/find joystick #%d",n);
    return FALSE;
  }

  // Set data format to "simple joystick"
  hr=dxJoy->SetDataFormat(&c_dfDIJoystick);
  if(FAILED(hr))
  { qwarn("QDXJoy: can't set data format for joystick #%d",n);
    return FALSE;
  }

  // Set cooperative level
  // Currently we want exclusive access for our main window
  hr=dxJoy->SetCooperativeLevel(QSHELL->GetQXWindow()->GetHWND(),
    //DISCL_EXCLUSIVE|DISCL_BACKGROUND);
    DISCL_EXCLUSIVE|DISCL_FOREGROUND);
  if(FAILED(hr))
  { qwarn("QDXJoy: can't set cooperative level for joystick #%d (%s)",
      n,qDXInput->Err2Str(hr));
    qlog(QLOG_INFO,"hdc=0x%x, err_invalidparam=%x, notinitialized=%x, e_handle=%x\n",
      QSHELL->GetQXWindow()->GetHDC(),DIERR_INVALIDPARAM,DIERR_NOTINITIALIZED,E_HANDLE);
    return FALSE;
  } else
  {
    qlog(QLOG_INFO,"hr=%x (%s)\n",hr,qDXInput->Err2Str(hr));
  }

  // Determine how many axes the joystick has
  diDevCaps.dwSize=sizeof(DIDEVCAPS);
  hr=dxJoy->GetCapabilities(&diDevCaps);
  if(FAILED(hr))
  { qwarn("QDXJoy: can't get capabilities for joystick #%d",n);
    return FALSE;
  }
qlog(QLOG_INFO,"Joystick found, FF=%d\n",IsForceFeedback());

  // Enumerate the axes and set the range of all axes to match something decent
  dxJoy->EnumObjects(EnumAxesCallback,(void*)this,DIDFT_AXIS);
#endif
  return TRUE;
}

void QDXJoy::Close()
// Close the joystick device
{
#ifdef WIN32
  if(dxJoy)
  {
    Unacquire();
    dxJoy->Release();
    dxJoy=0;
  }
#endif
}

/**********
* ACQUIRE *
**********/
bool QDXJoy::Acquire()
// Acquire the joystick for use
{
#ifdef WIN32
  if(dxJoy)
  { dxJoy->Acquire();
  } else return FALSE;
#endif
  return TRUE;
}
bool QDXJoy::Unacquire()
// Unacquire the joystick; somebody else may have it
{
#ifdef WIN32
  if(dxJoy)
  { dxJoy->Unacquire();
  } else return FALSE;
#endif
  return TRUE;
}

/**********
* POLLING *
**********/
void QDXJoy::Poll()
{
#ifdef WIN32
  DIJOYSTATE js;
  HRESULT hr;

  if(!dxJoy)return;

  // Hm, does this ever end the loop?
  do
  {
    hr=dxJoy->Poll();
    if(FAILED(hr))
    {
      static int count;
      // Don't overdue error reporting
      if(count<5)
      { qwarn("QDXJoy:Poll(); can't poll controller (%s)",qDXInput->Err2Str(hr));
        if(++count==5)
          qwarn("QDXJoy: too many poll failures; no more reporting");
      }
      //return;
    }

    // Get the device's state
    hr=dxJoy->GetDeviceState(sizeof(DIJOYSTATE),&js);
    if(hr==DIERR_INPUTLOST)
    {
      // Input stream has been interrupted
      // Reacquire and try to move on
      hr=dxJoy->Acquire();
      if(FAILED(hr))
      { qwarn("QDXJoy:Poll(); input lost and can't reacquire, err=%x",hr);
        return;
      }
    }
  } while(hr==DIERR_INPUTLOST);

  if(FAILED(hr))
  { 
    qwarn("QDXJoy: Can't poll controller");
    return;
  }
  // Take over joystick state
  x=js.lX;
  y=js.lY;
  z=js.lZ;
  rx=js.lRx;
  ry=js.lRy;
  rz=js.lRz;
  for(int i=0;i<MAX_BUTTON;i++)
    button[i]=(js.rgbButtons[i]&0x80)>>7;
  // There are still 2 axes and the POV which we may use...
#endif
}

/*************************
* FORCE FEEDBACK SUPPORT *
*************************/
bool QDXJoy::IsForceFeedback()
// Returns TRUE if the joystick is capable of force feedback
{
#ifndef WIN32
  return FALSE;
#else
  if(!dxJoy)return FALSE;
  // Capabilities are already reported in diDevCaps;
  if(diDevCaps.dwFlags&DIDC_FORCEFEEDBACK)
    return TRUE;
  return FALSE;
#endif
}
bool QDXJoy::DisableAutoCenter()
// Turn off auto-centering for joystick
// Returns TRUE if succesful
{
#ifdef WIN32
  DIPROPDWORD dw;
  HRESULT hr;

  dw.diph.dwSize=sizeof(DIPROPDWORD);
  dw.diph.dwHeaderSize=sizeof(DIPROPHEADER);
  dw.diph.dwObj=0;
  dw.diph.dwHow=DIPH_DEVICE;
  dw.dwData=0; //DIPROPAUTOCENTER_OFF;
  hr=dxJoy->SetProperty(DIPROP_AUTOCENTER,&dw.diph);
  if(FAILED(hr))
  { qwarn("QDXJoy:DisableAutoCenter() failed (%s)",qDXInput->Err2Str(hr));
    return FALSE;
  }
  return TRUE;
#else
  return FALSE;
#endif
}
bool QDXJoy::EnableAutoCenter()
// Turn on auto-centering for joystick
// Returns TRUE if succesful
{
#ifdef WIN32
  DIPROPDWORD dw;
  HRESULT hr;

  dw.diph.dwSize=sizeof(DIPROPDWORD);
  dw.diph.dwHeaderSize=sizeof(DIPROPHEADER);
  dw.diph.dwObj=0;
  dw.diph.dwHow=DIPH_DEVICE;
  dw.dwData=DIPROPAUTOCENTER_ON;
  hr=dxJoy->SetProperty(DIPROP_AUTOCENTER,&dw.diph);
  if(FAILED(hr))
  { qwarn("QDXJoy:DisableAutoCenter() failed (%s)",qDXInput->Err2Str(hr));
    return FALSE;
  }
  return TRUE;
#else
  return FALSE;
#endif
}

/*************************
* FORCE FEEDBACK EFFECTS *
*************************/
QDXFFEffect::QDXFFEffect(QDXJoy *joy)
{
#ifdef WIN32
  dxJoy=joy;
  pDIEffect=0;
#endif
}
QDXFFEffect::~QDXFFEffect()
{
#ifdef WIN32
  Release();
#endif
}

void QDXFFEffect::Start()
{
#ifdef WIN32
  if(pDIEffect)
  { HRESULT hr;
    hr=pDIEffect->Start(1,0);
    if(FAILED(hr))
    { qwarn("Can't start FF effect (%s)",qDXInput->Err2Str(hr));
    }
  }
#endif
}
void QDXFFEffect::Stop()
{
#ifdef WIN32
  if(pDIEffect)
    pDIEffect->Stop();
#endif
}
void QDXFFEffect::Release()
{
#ifdef WIN32
  if(pDIEffect)
  { pDIEffect->Release();
    pDIEffect=0;
  }
#endif
}

bool QDXFFEffect::SetupConstantForce()
// Create an effect for a constant force. Returns TRUE if ok.
{
#ifdef WIN32
  HRESULT hr;
  DWORD rgdwAxes[2]={ DIJOFS_X,DIJOFS_Y };
  LONG  rglDirection[2]={ 0,0 };

  ZeroMemory(&diEffect,sizeof(diEffect));
  force.constantForce.lMagnitude=0;
//force.constantForce.lMagnitude=5000;
  diEffect.dwSize=sizeof(DIEFFECT);
  diEffect.dwFlags=DIEFF_CARTESIAN|DIEFF_OBJECTOFFSETS;
  diEffect.dwDuration=INFINITE;
  diEffect.dwSamplePeriod=0;
  diEffect.dwGain=DI_FFNOMINALMAX;
  diEffect.dwTriggerButton=DIEB_NOTRIGGER;
  diEffect.dwTriggerRepeatInterval=0;
  diEffect.cAxes=2;
  diEffect.rgdwAxes=rgdwAxes;
  diEffect.rglDirection=rglDirection;
  diEffect.lpEnvelope=0;
  diEffect.cbTypeSpecificParams=sizeof(DICONSTANTFORCE);
  diEffect.lpvTypeSpecificParams=&force.constantForce;
  diEffect.dwStartDelay=0;

  hr=dxJoy->GetDXJoy()->CreateEffect(GUID_ConstantForce,&diEffect,&pDIEffect,0);
  if(FAILED(hr))
  {
    qwarn("QDXFFEffect:SetupConstantForce() failed (%s)",qDXInput->Err2Str(hr));
    return FALSE;
  }
  return TRUE;
#endif
}

bool QDXFFEffect::UpdateConstantForce(int fx,int fy)
// Update the constant force effect
{
#ifdef WIN32
  HRESULT hr;
  LONG  rglDirection[2]={ fx,fy };
  int len;

  ZeroMemory(&diEffect,sizeof(diEffect));
  len=sqrt((float)(fx*fx+fy*fy));
  force.constantForce.lMagnitude=len;
  diEffect.dwSize=sizeof(DIEFFECT);
  diEffect.dwFlags=DIEFF_CARTESIAN|DIEFF_OBJECTOFFSETS;
  diEffect.cAxes=2;
  diEffect.rglDirection=rglDirection;
  diEffect.lpEnvelope=0;
  diEffect.cbTypeSpecificParams=sizeof(DICONSTANTFORCE);
  diEffect.lpvTypeSpecificParams=&force.constantForce;
  diEffect.dwStartDelay=0;

  hr=pDIEffect->SetParameters(&diEffect,DIEP_DIRECTION|DIEP_TYPESPECIFICPARAMS|DIEP_START);
  if(FAILED(hr))
  {
    qwarn("QDXFFEffect:UpdateConstantForce() failed (%s)",qDXInput->Err2Str(hr));
    return FALSE;
  }
  return TRUE;
#endif
}