Not long ago I needed a whole bunch of head trackers for just one week. Not wanting to invest tens of thousands of dollars in high-end tracking systems, I came up with an easy DIY head tracking system constructed from the guts of a Gyration Air Mouse. The Air Mouse contains a 2 degree of freedom (2 DOF – Yaw and Elevation) orientation sensor. In my application, users didn’t really roll their head (i.e. tilt their head from side to side), so 2 DOF actually worked pretty well. The tracker is really responsive with very little lag. I used Gyration’s Go Pro Air Mouse, but I suspect any of their Air Mouse family can be made to work. This article presents the hardware modification in detail and source code to integrate the tracker into your own software.
A quick disclaimer… The resulting head tracker will exhibit some drift over time. Its not perfect. However for short gaming sessions or experimental use, it is great. Also there are some good methods, explained at the end of this article, to minimize the drift.
Step 1 – Tear out the guts of the mouse.
The Air Mouse is way too heavy to put on your head. Fortunately most of the weight is in the case and battery back, neither of which is needed. The actual circuit boards weigh less than an ounce, perfect for head attire.
Step 2 – Extract the Circuit Boards
Step 3 – Remove the unnecessary junk from the circuit boards
There’s a bunch of stuff that just adds weight and size that we won’t be using. You’ll need a decent soldering iron, an X-acto knife, and either a solder sucker or solder removal braid.
Step 4 – Remove more junk…
Step 5 – Remove the power connectors and bypass Air-mode switch
The battery contacts are a nuisance and might short themselves out later on. We’ll remove them. Also the Air Mouse has an inconvenient 2D/3D switch. We’ll bypass it so that the tracker is always in 3D Air Mode, not 2D Mouse Mode.
Step 6 – Trim some extra circuit board away to make it smaller.
Step 7 – Setup the new power source.
I chose to keep my head tracker as a “wired” device, the wire being a 3 VDC power cable. However, you can choose to make it wireless at the expense of some extra battery weight.
Step 8 – Reassemble the boards.
Step 9 – Hooking up the head tracker
Its time to connect everything and fire it up!
Step 10 – Packaging
I made a little thermo-form enclosure out of thin textured styrene. The cube is positioned bottom down. Rather than make the enclosure thicker, I chose to have the cube stick out the bottom, inasmuch as the velcro I used to secure the tracker to the helmet matches the protruding height of the cube.
Step 11 – The Software
To Windows, the Gyration looks like a mouse. Most PC games handle mouse input directly and you won’t need any added drivers or software at all.
If you’re writing your own game, the code below provides a basic low level interface in raw mode to avoid window focus issues. You will still need to scale the coordinates so that one revolution of your head translates into one revolution inside the game space.
Call InitRawMouse() once at the beginning of your application to set up your tracker. Then call getRawMouse() whenever you need Yaw and Elevation
The code works just fine, but doesn’t do anything graceful with error conditions. Feel free to clean it up!
#if (_WIN32_WINNT < 0x0501) #undef _WIN32_WINNT #define _WIN32_WINNT 0x0501 #endif
#include #include
RAWINPUTDEVICE Rid[1];
void InitRawMouse() { Rid[0].usUsagePage = 0x01; Rid[0].usUsage = 0x02; Rid[0].dwFlags = RIDEV_NOLEGACY; // adds HID mouse and ignores legacy messages Rid[0].hwndTarget = 0; if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { //registration failed. Call GetLastError for the cause of the error } }
Int getRawMouse(LPARAM lParam, long *x, long *y) { UINT dwSize; RAWINPUT lpb;
GetRawInputData((HRAWINPUT)lParam, RID_INPUT, NULL, &dwSize, sizeof(RAWINPUTHEADER));
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, &lpb, &dwSize, sizeof(RAWINPUTHEADER)) != dwSize ) { return 0; }
if (lpb.header.dwType == RIM_TYPEMOUSE) { *x += lpb.data.mouse.lLastX; *y += lpb.data.mouse.lLastY; } return 0; }
/* Below is a sample of what your Windows event loop might look like. Look for a WM_INPUT message and pass it to the function OnInput() which calls getRawMouse() to update two static variables: Mouse_x and Mouse_y. Somewhere else in your program you probably want to scale Mouse_x and Mouse_y to be degrees, radians, or whatever form your program represents angles in. */
OnInput(LPARAM lparam) { getRawMouse(lparam, &Mouse_x, &Mouse_y); }
LONG FAR PASCAL WinVisProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { switch (msg) { // Other messages case WM_INPUT: // WM_INPUT OnInput(lparam); break; } return (DefWindowProc(hwnd, msg, wparam, lparam)); }
Step 12 – How to minimize tracker drift
I discovered three different ways to minimize the inherent drift in the Gyration sensors:
- Most of the drift resulted from dropped RF updates from the transmitter to the receiver. Even though the Gyration is spec’ed for 100 ft. of distance between the transmitter and receiver, I found that it started to drop updates at around 5-10 feet, or more. That’s not a problem as an air mouse which is a relative device, but is troublesome for an absolute orientation device such as a head tracker. My solution was to put the receiver at the end of a long USB extender cable so that it remained close to the tracker transmitter. In retrospect, given that I worked with the “wired” power configuration described above, I could have brought the USB cable up to the VR helmet and tracker transmitter, thus putting the receiver within inches of the transmitter and tapping power directly from the USB port.
- Some drift is induced by turning the tracker very rapidly; on the order of 200 deg. per second, or more. In practice, this is quite difficult to do while wearing a helmet. Neck injury will likely precede any drift.
- As a failsafe, I used an extra button on my hand controller which reset (in software) the mouse X/Y position to horizontal/forward. If the tracker drifted to far, the user could simply hold their head in a normal forward position and click the hand controller reset to get the tracker back in sync with their orientation.