Hi,
Feel free to use this in any way. If you put this in a hard copy settings (book, article) an acknowledgment to me and to the sources I have quoted would be nice. If you are working with Input, 3D Navigation or 3D Interfaces, please PM me to see if we can collaborate in publications.
I have a 3D Connexion (3DX) which provides 6DOF. While I'm not so happy with the movement as of now, I want to share my code. Next week I will try get the wii mote working... At the end, I will add all of it to my upcoming tutorial, "First Person Shooter" tutorial that I'm preparing. So far, I have the gamepad using direct input and 3dx navigator working. I will be adding some additional input devices.
This is a "HOW TO" / Tutorial that I will be soon adding to the wiki, once I get feedback from the community. Please note that this is not the only option to read the data from the navigator or the only way to integrate it with windows. I'm looking for an alternative which I will post, to allow to have it as part of my game loop as opposed to have it signal every time there is data.
It is always important to give credit where credit's due. Here it goes:
The code here is a combination of
- Kojack code from one of his many post.
- C++ Professional Book GREAT BOOK
- sample called WM_INPUT found in the example ftp site of 3dx. To access the FTP Site, username is examples and password is the same (examples)
- my own code inside (do I? )
- I have it working in the Advanced Framework provided here in the ogre tutorials.
========================================
You will need to use the observer design pattern. Here is the code
Code: Select all
//Win3DXOgreListener.h
#ifndef WIN3DXOGRELISTENER_H
#define WIN3DXOGRELISTENER_H
#include <OgreVector3.h>
#include <OgreVector4.h>
typedef struct DOF6 {
int tx;
int ty;
int tz;
int rx;
int ry;
int rz;
} DOFSix;
typedef struct BDATA {
unsigned char p3;
unsigned char p2;
unsigned char p1;
} BData;
class Win3DXOgreListener
{
public:
virtual bool handleNavigator(int message,DOFSix & D,BData & B) = 0;
};
#endif
Code: Select all
//Win3DXOgreEventRegistry.h
#ifndef WINSDKOGREEVENTREGISTRY_H
#define WINSDKOGREEVENTREGISTRY_H
#include "Win3DXOgreListener.h"
#include <vector>
#include <map>
class Win3DXOgreEventRegistry
{
public:
static bool registerListener(int inMessage, Win3DXOgreListener* inListener);
static bool signalNavigator(int inMessage, int outMessage,DOFSix & D,BData & B);
enum MESSAGES {M_UNKNOWN = 100,M_BUTTON = 101,M_NAVIGATOR = 102};
enum REGISTER {LISTEN_ALL=0};
protected:
static std::map<int, std::vector<Win3DXOgreListener*>> sListenerMap;
};
#endif
Code: Select all
// Win3DXOgreEventRegistry.cpp
// Implements the EventRegistry class
#include "Win3DXOgreEventRegistry.h"
#include <iostream>
using namespace std;
// Define the static map.
map<int, vector<Win3DXOgreListener*>> Win3DXOgreEventRegistry::sListenerMap;
bool Win3DXOgreEventRegistry::registerListener(int inMessage, Win3DXOgreListener* inListener)
{
if (inMessage != REGISTER::LISTEN_ALL) return false;
sListenerMap[inMessage].push_back(inListener);
return true;
}
bool Win3DXOgreEventRegistry::signalNavigator(int inMessage, int outMessage,DOFSix & D,BData & B)
{
// Check to see if the message has *any* listeners. This check is required
// because otherwise, accessing sListenerMap[inMessage] would create
// a new entry when it’s not yet in the map. See Chapter 12.
if (inMessage != REGISTER::LISTEN_ALL || sListenerMap.find(inMessage) == sListenerMap.end())
return false;
for (auto iter = sListenerMap[inMessage].begin();
iter != sListenerMap[inMessage].end(); ++iter) {
(*iter)->handleNavigator(outMessage, D, B);
}
return true;
}
The code is simple. First, Win3DXOgreListener class is used to be class that will be inherited to handle the messages. For example, I have it added to AdvancedOgreFramework and AppState to be able to handle this
Code: Select all
class OgreFramework : public Ogre::Singleton<OgreFramework>, OIS::KeyListener, OIS::MouseListener , Win3DXOgreListener
{
...
bool handleNavigator(int message,DOFSix & D, BData & B);
...
};
Code: Select all
class GameState : public AppState
{
...
bool handleNavigator(int message,DOFSix & D, BData & B);
};
Now the code that in my case, I placed in top of the file AdvancedFramework.cpp
the first part of the code, shows what is needed to collect information from raw devices, but in specific for the 3dx navigator.
Code: Select all
//***************************************************************************
//3DX Specific
//***************************************************************************
// ---------------- RawInput ------------------
#include <crtdbg.h>
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
//****************************************************************************
//
PRAWINPUTDEVICELIST g_pRawInputDeviceList;
PRAWINPUTDEVICE g_pRawInputDevices;
int g_nUsagePage1Usage8Devices;
BOOL InitRawDevices(void)
{
// Find the Raw Devices
UINT nDevices;
// Get Number of devices attached
if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0)
{
OgreFramework::getSingletonPtr()->m_pLog->logMessage("[3DX] WARNING: No RawInput devices attached");
return FALSE;
}
// Create list large enough to hold all RAWINPUTDEVICE structs
if ((g_pRawInputDeviceList = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * nDevices)) == NULL)
{
OgreFramework::getSingletonPtr()->m_pLog->logMessage("[3DX] Error: malloc RAWINPUTDEVICELIST");
return FALSE;
}
// Now get the data on the attached devices
if (GetRawInputDeviceList(g_pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST)) == -1)
{
OgreFramework::getSingletonPtr()->m_pLog->logMessage("[3DX] Error: GetRawInputDeviceList");
return FALSE;
}
g_pRawInputDevices = (PRAWINPUTDEVICE)malloc( nDevices * sizeof(RAWINPUTDEVICE) );
g_nUsagePage1Usage8Devices = 0;
// Look through device list for RIM_TYPEHID devices with UsagePage == 1, Usage == 8
for(UINT i=0; i<nDevices; i++)
{
if (g_pRawInputDeviceList[i].dwType == RIM_TYPEHID)
{
UINT nchars = 300;
TCHAR deviceName[300];
if (GetRawInputDeviceInfo( g_pRawInputDeviceList[i].hDevice,
RIDI_DEVICENAME, deviceName, &nchars) >= 0)
fprintf(stderr, "Device[%d]: handle=0x%x name = %S\n", i, g_pRawInputDeviceList[i].hDevice, deviceName);
RID_DEVICE_INFO dinfo;
UINT sizeofdinfo = sizeof(dinfo);
dinfo.cbSize = sizeofdinfo;
if (GetRawInputDeviceInfo( g_pRawInputDeviceList[i].hDevice,
RIDI_DEVICEINFO, &dinfo, &sizeofdinfo ) >= 0)
{
if (dinfo.dwType == RIM_TYPEHID)
{
RID_DEVICE_INFO_HID *phidInfo = &dinfo.hid;
fprintf(stderr, "VID = 0x%x\n", phidInfo->dwVendorId);
fprintf(stderr, "PID = 0x%x\n", phidInfo->dwProductId);
fprintf(stderr, "Version = 0x%x\n", phidInfo->dwVersionNumber);
fprintf(stderr, "UsagePage = 0x%x\n", phidInfo->usUsagePage);
fprintf(stderr, "Usage = 0x%x\n", phidInfo->usUsage);
// Add this one to the list of interesting devices?
// Actually only have to do this once to get input from all usage 1, usagePage 8 devices
// This just keeps out the other usages.
// You might want to put up a list for users to select amongst the different devices.
// In particular, to assign separate functionality to the different devices.
if (phidInfo->usUsagePage == 1 && phidInfo->usUsage == 8)
{
g_pRawInputDevices[g_nUsagePage1Usage8Devices].usUsagePage = phidInfo->usUsagePage;
g_pRawInputDevices[g_nUsagePage1Usage8Devices].usUsage = phidInfo->usUsage;
g_pRawInputDevices[g_nUsagePage1Usage8Devices].dwFlags = 0;
g_pRawInputDevices[g_nUsagePage1Usage8Devices].hwndTarget = NULL;
g_nUsagePage1Usage8Devices++;
}
}
}
}
}
// Register for input from the devices in the list
if (RegisterRawInputDevices( g_pRawInputDevices, g_nUsagePage1Usage8Devices, sizeof(RAWINPUTDEVICE) ) == FALSE )
{
OgreFramework::getSingletonPtr()->m_pLog->logMessage("[3DX] Error: calling RegisterRawInputDevices");
return FALSE;
}
return TRUE;
}
void FreeRawInputDevices(void)
{
free(g_pRawInputDeviceList);
}
void ProcessWM_INPUTEvent( LPARAM lParam )
{
#ifdef SHOW_DETAILS
fprintf(stderr, "WM_INPUT lParam=0x%x\n", lParam );
#endif
RAWINPUTHEADER header;
UINT size = sizeof(header);
if ( GetRawInputData( (HRAWINPUT)lParam, RID_HEADER, &header, &size, sizeof(RAWINPUTHEADER) ) == -1)
{
OgreFramework::getSingletonPtr()->m_pLog->logMessage("[3DX] Error: GetRawInputData(RID_HEADER)");
return;
}
else
{
fprintf(stderr, "rawEvent.header: hDevice = 0x%x\n", header.hDevice );
}
// Set aside enough memory for the full event
size = header.dwSize;
LPRAWINPUT event = (LPRAWINPUT)malloc(size);
if (GetRawInputData( (HRAWINPUT)lParam, RID_INPUT, event, &size, sizeof(RAWINPUTHEADER) ) == -1)
{
OgreFramework::getSingletonPtr()->m_pLog->logMessage("[3DX] Error: GetRawInputData(RID_INPUT)");
return;
}
else
{
if (event->header.dwType == RIM_TYPEHID)
{
static BOOL bGotTranslation = FALSE,
bGotRotation = FALSE;
static int all6DOFs[6] = {0};
LPRAWHID pRawHid = &event->data.hid;
#ifdef SHOW_DETAILS
fprintf(stderr, "rawInput count: %d\n", pRawHid->dwCount);
fprintf(stderr, " size: %d\n", pRawHid->dwSizeHid);
for(UINT i=0; i<pRawHid->dwSizeHid; i++)
{
fprintf(stderr, "%d ", pRawHid->bRawData[i] );
}
_RPT0( _CRT_WARN, "\n" );
#endif
// Translation or Rotation packet? They come in two different packets.
if (pRawHid->bRawData[0] == 1) // Translation vector
{
all6DOFs[0] = (pRawHid->bRawData[1] & 0x000000ff) | ((signed short)(pRawHid->bRawData[2]<<8) & 0xffffff00);
all6DOFs[1] = (pRawHid->bRawData[3] & 0x000000ff) | ((signed short)(pRawHid->bRawData[4]<<8) & 0xffffff00);
all6DOFs[2] = (pRawHid->bRawData[5] & 0x000000ff) | ((signed short)(pRawHid->bRawData[6]<<8) & 0xffffff00);
bGotTranslation = TRUE;
}
else if (pRawHid->bRawData[0] == 2) // Rotation vector
{
all6DOFs[3] = (pRawHid->bRawData[1] & 0x000000ff) | ((signed short)(pRawHid->bRawData[2]<<8) & 0xffffff00);
all6DOFs[4] = (pRawHid->bRawData[3] & 0x000000ff) | ((signed short)(pRawHid->bRawData[4]<<8) & 0xffffff00);
all6DOFs[5] = (pRawHid->bRawData[5] & 0x000000ff) | ((signed short)(pRawHid->bRawData[6]<<8) & 0xffffff00);
bGotRotation = TRUE;
}
else if (pRawHid->bRawData[0] == 3)
{// Buttons (display most significant byte to least)
DOFSix D;
BData B;
//
D.tx = 0;
D.ty = 0;
D.tz = 0;
D.rx = 0;
D.ry = 0;
D.rz = 0;
B.p3 = (unsigned char)pRawHid->bRawData[3];
B.p2 = (unsigned char)pRawHid->bRawData[2];
B.p1 = (unsigned char)pRawHid->bRawData[1];
Win3DXOgreEventRegistry::signalNavigator(Win3DXOgreEventRegistry::LISTEN_ALL,
Win3DXOgreEventRegistry::M_BUTTON,
D,B);
fprintf(stderr, "Button mask: %.2x %.2x %.2x\n",(unsigned char)pRawHid->bRawData[3],(unsigned char)pRawHid->bRawData[2],(unsigned char)pRawHid->bRawData[1]);
}
if (bGotTranslation && bGotRotation)
{
DOFSix D;
BData B;
//
D.tx = all6DOFs[0];
D.ty = all6DOFs[1];
D.tz = all6DOFs[2];
D.rx = all6DOFs[3];
D.ry = all6DOFs[4];
D.rz = all6DOFs[5];
B.p3 = 0;
B.p2 = 0;
B.p1 = 0;
Win3DXOgreEventRegistry::signalNavigator(Win3DXOgreEventRegistry::LISTEN_ALL,
Win3DXOgreEventRegistry::M_NAVIGATOR,
D,B);
bGotTranslation = bGotRotation = FALSE;
fprintf(stderr, "RAW DATA all6DOFs: T=[%d,%d,%d] ", all6DOFs[0], all6DOFs[1], all6DOFs[2]);
fprintf(stderr, " R=[%d,%d,%d]\n", all6DOFs[3], all6DOFs[4], all6DOFs[5]);
}
}
}
}
and later the code that kojack had provided. which I added in AdvancedFramework.cpp as well, right below the other code.
Code: Select all
//***************************************************************************
//Handle raw messages for 3dx and later called ogre.
//***************************************************************************
WNDPROC g_ogreWinProc2 = NULL;
LRESULT APIENTRY SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
// handle your WM_**** stuff here
case WM_INPUT:
ProcessWM_INPUTEvent( lParam );
break;
}
return CallWindowProc(g_ogreWinProc2, hwnd, uMsg, wParam, lParam);
}
in the destructor of advancedFramework class, I have added at the end
Code: Select all
if (m_bRawDevicesOn)
FreeRawInputDevices();
Code: Select all
//init Navigator
if (!InitRawDevices())
{
OgreFramework::getSingletonPtr()->m_pLog->logMessage("[3DX] Error InitRawDevices()");
m_bRawDevicesOn = false;
}
else
{
m_bRawDevicesOn = true;
HWND hwnd;
OgreFramework::getSingletonPtr()->m_pRenderWnd->getCustomAttribute("WINDOW", (void*)&hwnd);
g_ogreWinProc2 = (WNDPROC) SetWindowLong(hwnd, GWL_WNDPROC, (LONG) SubclassProc);
Win3DXOgreEventRegistry::registerListener(Win3DXOgreEventRegistry::LISTEN_ALL,
this);
}
Code: Select all
bool GameState::handleNavigator(int message,DOFSix & D, BData & B)
{
if (message == Win3DXOgreEventRegistry::M_NAVIGATOR)
{
long sumAll = D.tx+D.ty+D.tz+D.rx+D.ry+D.rz;
//3DX keeps sending signal for a little while ...
if (sumAll == 0)
return true;
if (D.tx != 0)
m_TranslateVector.x = m_MoveScale * (float)D.tx * m_Factor3DX;
if (D.tz != 0)
m_TranslateVector.y = m_MoveScale * (float)D.tz * -m_Factor3DX;
if (D.ty != 0)
m_TranslateVector.z = m_MoveScale * (float)D.ty * m_Factor3DX;
//yaw z axis ???
//roll x axis ???
//pitch y axis ???
if (D.ry != 0)
m_pCamera->yaw(Degree(D.rz * -m_Factor3DX * 0.1)) ;
if (D.rx != 0)
m_pCamera->pitch(Degree(D.rx * -m_Factor3DX * 0.1));
if (D.ry != 0)
m_pCamera->roll(Degree(D.ry * -m_Factor3DX * 0.1));
moveCamera();
fprintf(stderr,"M_NAVIGATOR T=[%d,%d,%d] R=[%d,%d,%d]\n",D.tx,D.ty,D.tz,D.rx,D.ry,D.rz);
}
else if (message == Win3DXOgreEventRegistry::M_BUTTON)
{
//I'm only using navigator has two buttons
int bNum = B.p3 | B.p2 | B.p3;
fprintf(stderr,"M_BUTTON Number = %d\n",bNum);
}
else
{
fprintf(stderr,"Called without proper message???\n");
}
return true;
}
My setup is Ogre 1.7.x (32 bit) and Windows 7 64 bit with Visual Studio 2010.
Important is to note that navigator translation X may not be the X in your screen and the same for the rest. Since the navigator has only two buttons, my buttons come as button 1 and button 2 (1 and 2) . Also know that the events for WM_INPUT when is not buttons, come in 2 separate events, that I combined into one.
I use fprintf for my console screen and the log only for important aspects of Ogre.
I hope you like this.
PS: Grammar correction are WELCOME!
************ MARCH - 03 - 2012
Three additional comments.
1) If you use the WM_INPUT, it is my understanding that you are not using their drivers. This means that if you make changes in their control panel application, none of this will matter. This is "RAW DATA"
2) This is how I currently handle the event .. it is not perfect. As I update this thread, I will update this part of the code as well. The code has been posted in the original message. Look for Partial GameState.cpp
3) I didn't mention anything about the two structs thare are used to passed the messages
typedef struct DOF6 {
int tx;
int ty;
int tz;
int rx;
int ry;
int rz;
} DOFSix;
typedef struct BDATA {
unsigned char p3;
unsigned char p2;
unsigned char p1;
} BData;
DOFSix provides the translation (T) components and rotation (R) components. Tx,Ty,Tz,Rx,Ry,Rz.
BData has the button information. Since I have the navigator, I only have 2 buttons. This gets identified in p1 as 1 or 2. It is very likely that if you binary or them when you have more buttons will yield the correct result. You must do P = p3 | p2 | p1 (see the ProcessWM_INPUTEvent method for more information about this.)
************