Tuesday, January 7, 2014

Oculus Rift Position Tracking with Magnetic Field Experiment

Over the last few days I have been experimenting with ways to add positional tracking to the Oculus Rift.  After watching some of the CES videos it seem that an optical tracking system is working pretty well for Oculus, but I wanted to try implementing a magnetic tracking setup, similar to the Razer Hydra.  I built a quick prototype to run some tests.  The nice thing about this system is that it doesn't require any modifications to the rift itself, just a coil of wire to generate a magnetic field.  Another advantage is that it doesn't require line of sight, which might be important if a player decides to duck under their desk.


Shown here is a coil of magnet wire fed by a 5 volt 5 amp power supply.  The wire is the green 26 gauge magnet wire from radio shack, I used an entire spool(75 feet) which gave ~80 coils with a 9 cm diameter.  The resistance of the coil was 2.5 ohms, which give 2 amps of current through the coil.

Mathematical Details
In case anyone is interested I am going to detail the math for how I did the position tracking, If you aren't interested feel free to skip to the video linked a little further down.
Shown here is the magnetic field of a current loop (http://en.wikipedia.org/wiki/Magnetic_field), and the coordinate frame used in the Oculus Rift (at least in the sensor test program)  The magnetometer measures the direction and magnitude of the field lines, and we need to determine the distance and direction from the rift to the coil from the magnetic field being measured.  The distance is fairly easy, according to the Biot-Savart law the magnitude of the magnetic field decreases with the square of the distance, so the distance can be determined from the magnitude field with the following equation.

d = 1/sqrt(B) * C
Where d is the distance between the sensor and the coil, B is the magnitude of the magnetic field, and C is a scaling constant we can find via calibration.

Finding the direction from the coil to the rift is a bit more complicated, and requires a few assumptions.  If the sensor is at the same height as the rift, you can see from the diagram that the field lines will point straight up. Therefore, the magnetic field at any point at this height will look the same, and it will be impossible to determine the location of the rift.  Additionally, the magnetic field above the coil is mirrored below the coil, so each position above the coil will have a corresponding position below and to the opposite side of the coil that will have a matching magnetic field line.  Obtaining an accurate position from a single coil therefore requires the assumption that the sensor is always above the coil.  For the most part this is fine because the sensor is attached to a persons head, and the coil will sit well below the persons head on a desk.

I decided to break the direction problem into two sub problems.  First the direction vector in the X-Z plane is found.  The magnetic field lines circle out from the center, so at any point above the coil the X and Z components of the magnetic field will point toward the center of the coil.  The X-Z direction then is just the reverse of the X-Z component of the magnetic field.  The following set of screen shots shows the position of the rift in the horizontal plane, and magnetic field line measured by the sensor. 


The second sub problem is the height of the sensor.  To find the height of the sensor I developed a set of equations to calculate the inclination angle based on the vertical component of the magnetic field divided by the horizontal component of the magnetic field.

This equation was developed by calculating the magnetic field of the coil at a set of test points using matlab, then using a polynomial fit to generate a polynomial equation for the inclination angle based on the measured magnetic field.  First I wrote a matlab script to calculate the magnetic field at a set of points in the X-Y plane using the Biot-Savart law.  To verify that the math was correct I outputted the results as a vector field, which I could then compare to the diagram pictured above.
The diagram shows the magnetic field curling around the edge of the coil, 45 mm from the center of the coil.  I normalized the magnitude of each vector because in this case I was more concerned about the direction of the field than the magnitude.  Once the math was verified I altered the script to take the distance from the center(d) and the inclination angle(phi) as inputs, and output the vertical component divided by the horizontal component.  The following diagram shows a 3D plot of the results, I used distances from 0 to 200 mm, spaced every 25 mm, and inclination angle from 0 to pi/4, spaced every 0.01 radians.
Next I separated the test points at each distance increment into separate data series, and did a polynomial fit at each distance to find an equation for the inclination angle based on the magnetic field.  When calculating the inclination angle the program selects which equation to used based on the distance between the sensor in the rift and the coil.
Polynomial fit for d = 50 mm

Polynomial fit for d = 100 mm

Polynomial fit for d =150 mm

Polynomial fit for d = 200 mm

Beyond 200 mm the magnetic field varies only with inclination angle, so the final equation applies to every distance beyond 200 mm.  Here are a few pictures of the setup with proper height sensing.  At this point I am moving the coil and not the goggles.  I have modified the test app to zero out the ambient magnetic field, so I am only measuring the magnetic field from the coil.

More technical details
The Earth's magnetic field is much stronger than the magnetic field produced by the coil, so the coil is not detectable without a way to separate the Earth magnetic field and the coil magnetic field.  I solved this by making a simple Arduino circuit to pulse the current through the coil, 10 ms on 40 ms off.  By modulating the current in the coil, the sensor can take readings with the coil on and with the coil off, and the difference of the two is the magnetic field from just the coil.  Modulating the current also has the advantage of reducing the power consumption of the coil.  Finally, the magnetometer is quite noisy, so it was necessary to use the moving average filter provided with the SDK to get nice looking results.  Averaging several hundred samples gave smooth results, but using a moving average filter can slow the response time quite drastically.


The system tracks fairly accurately, but due to the moving average filter the response is quite slow.  For this system to be usable it would clearly need something like a Kalman filter to reduce the noise without slowing the response so drastically.  The range is also a bit disappointing, I think I will need to do some more research into how a device like the razer hydra attains a more acceptable range.  I'm not sure if it is just a matter of adding more turns to the coil, or if higher quality sensors are required.

Thanks for reading, and if anyone is interested in more details or would like to see my code let me know.