Showing posts with label SDK. Show all posts
Showing posts with label SDK. Show all posts

Monday, August 4, 2014

0.4 SDK: Users of unknown gender now have a gender of “Unknown”

Oculus recently released the 0.4 version of the SDK.  I am primarily a Mac developer and the fact that it isn’t available for the Mac yet is a big disappointment to me. So while I am impatiently waiting for the Mac version, I’ll poke around the Windows version. As the default profile settings were an irritation to me previously, I checked to see if Oculus made changes to the default profile settings and I am pleased to see that they have:

#define OVR_DEFAULT_GENDER                  "Unknown"

That’s right. Users who have not yet created a profile are no longer assumed to be “male”. Instead the default matches reality - when the user’s gender is unknown because the user hasn’t specified a gender, the SDK now returns a gender of “Unknown”.

This is a good step as you will no longer get false data from the SDK. Let's take a closer look at the profile default values

// Default measurements empirically determined at Oculus to make us happy
// The neck model numbers were derived as an average of the male and female averages from ANSUR-88
// NECK_TO_EYE_HORIZONTAL = H22 - H43 = INFRAORBITALE_BACK_OF_HEAD - TRAGION_BACK_OF_HEAD
// NECK_TO_EYE_VERTICAL = H21 - H15 = GONION_TOP_OF_HEAD - ECTOORBITALE_TOP_OF_HEAD
// These were determined to be the best in a small user study, clearly beating out the previous default values
#define OVR_DEFAULT_GENDER                  "Unknown"
#define OVR_DEFAULT_PLAYER_HEIGHT           1.778f
#define OVR_DEFAULT_EYE_HEIGHT              1.675f
#define OVR_DEFAULT_IPD                     0.064f
#define OVR_DEFAULT_NECK_TO_EYE_HORIZONTAL  0.0805f
#define OVR_DEFAULT_NECK_TO_EYE_VERTICAL    0.075f

#define OVR_DEFAULT_EYE_RELIEF_DIAL         3


It is also good to see that the "neck model numbers were derived as an average of the male and female averages." However, the height here remains as it has in past SDK versions at 1.778f - the average height of an adult male in the US. I'm a little wary of this value as Oculus doesn't indicate how varied the user pool used to determine this value was. They simply say "a small user study." Was this a varied user pool comprised equally of men and women? How varied were the heights of those in the user pool? Without that information or further study, I can't be sure that these values don't introduce bias and it is something I will keep track of in my own user tests.

I've been keeping such a close eye on this issue because I feel that what the writer Octavia Butler said about science fiction applies here and now to VR.

 "There are no real walls around science fiction. We can build them, but they’re not there naturally."
-- Octavia Butler

 There are no real walls around VR. Let's do what we can to not build those walls. VR is for everyone.







Sunday, June 8, 2014

Creating JOVR

When the Rift DK1 came out, the supplied software didn't support Linux, or Java or anything other than Windows by way of C++.  While other platform support for Linux and OSX were eventually added, and in the 0.3.x versions of the SDK, support for straight C was added as well, if you were a Java developer and wanted to work with the Rift you were out of luck.

Early on I set about digging into the SDK's inner workings to see how easily it could be ported to Java.  Eventually I succeeded in replicating both the head tracker reading code and some of the sensor fusion code in a Java project I called Jocular.  It was basically functional, but it didn't have all the same features as the C++ SDK.  In particular I never spent a lot of effort on magnetic yaw correction or prediction.  Largely I lost interest in it because I didn't want to make myself responsible for constantly porting stuff in order to maintain feature parity with the official codebase.

When the 0.3.x came out it included a simplified C API that provided not only access to the sensors but also included an implementation of the distortion rendering inside the SDK itself.  This was something of a boon to developers, because the implementation of distortion is non-trivial.  It was also a boon to Java developers, because tooling for creating Java bindings to C functionality is pretty functional.  That's not to say that binding the the C++ code would have been impossible, but the C API is much simpler, requiring fewer hurdles to jump to get the same results.  So I bumped the Jocular version from 1 to 2, and set about producing just such a binding.

JNA vs JNI

Binding from Java to C can be done in a couple of ways, typically either through JNI (Java Native Interface) or through JNA (Java Native Access).

JNI functionality requires that you write C code specifically designed for Java to call.  Within this C code you can then call other native C libraries, or C++ code for that matter.  This typically produces the fastest implementation, but it's a bit of overhead I wanted to avoid.

JNA on the other hand can be called with pure Java.  The magic of loading native libraries, locating the appropriate functions within them and conversion of parameters is all baked inside the JNA library, which internally uses JNI.  So really, there's only JNI for accessing C code, but the JNA library makes it easy to do in a non-library specific way.

JNA tends to be slower than JNI, but the difference tends to reflect how much information you're passing through parameters.  The Rift API is simple and doesn't require much information to be passed over the API.  The head tracking data and timing information is trivial in almost any context, while the OpenGL subsystem actually holds the bulk of the information that is used for distortion.  So despite the ostensible speed difference, the ease of use of JNA wins out here.

Building the binding

Actually creating the Java classes to map to the Oculus SDK C API was the next task, and not one I relished.  While the functions and structures I wanted to map weren't complex, there are a couple dozen of them, and going through them would have been tedious.  Fortunately, there exist tools to do this for me.  In particular I found a tool called JNAerator, which would accept as input a C header file and produce as output Java classes.  This was suitable for producing a good first pass implementation and was essentially what I used for the first release of Jocular 2.

JNAerator is kind of a finicky tool.  There's both a command line interface as well as a GUI of sorts, but neither is exactly what you'd think of as polished.  It's possible there are better tools out there, but I started working with this one and it seemed to be suitable for doing the bulk of the work I needed done, producing output classes for all the required structures, constants for all the required enums and static methods on a library wrapper for all the functions.

The mapping was imperfect.  For instance, the C API header declares the structures with underscores in their names and uses typedefs to produce non-underscored names for use.  The generated code didn't recognize the typedefs as important, so the generated types include the underscores.

All of the types also include ovr in the structure name, since C has no concept of namespacing, so all types are always in the global namespace.  Java has strong namespacing, so the naming verbosity is unnecessary.

So for instance, the OVR C API has a raw type name ovrSensorState_.  The Java name should be SensorState.  Fortunately, JNA doesn't care about the type names, just the signatures, so it was a simple matter of going through the API and using Eclipse to refactor the names.

The next problem was the access pattern for using the ovrHmd handle provided by the SDK.  This handle is essentially a pointer to a class in the C API implementation (which is written in C++), and most of the functions in the C API take the handle as their first parameter.  This pattern indicates that the best mapping for the type was to create a class that wrapped the SDK functions internally.  Fortunately the generator for the Java code was smart enough to recognize this pattern and create members on the Hmd type that wrapped calls to the static members in the library.  However, some of the calls were ripe for some improvement.

Some of the functions needed to return complex data but also indicate error state.  In these cases the C API provides an function parameter which is used for output, and the function return value itself is a flag, where a non-zero value indicates success.  In Java, it's preferable to use the exception handling in the language to indicate error conditions.  So I modified the wrappers for these functions to no longer require the user to pass in output parameters, and to raise an exception in the case of failure.

Consider the case of the rendering config function.  In the C API it's declared like this

ovrBool ovrHmd_ConfigureRendering(
  ovrHmd hmd,
  const ovrRenderAPIConfig* apiConfig,
  unsigned int distortionCaps,
  const ovrFovPort[2] eyeFovIn,
  ovrEyeRenderDesc[2] eyeRenderDescOut)
The generated code produces this equivalent (after my refactoring of the naming conventions):
byte ovrHmd_ConfigureRendering(  Hmd hmd,   RenderAPIConfig apiConfig,   int distortionCaps,   FovPort eyeFovIn[],   EyeRenderDesc eyeRenderDescOut[]);
This is made much more use friendly in the Hmd wrapper class like so:
public EyeRenderDesc[] configureRendering(    RenderAPIConfig apiConfig,     int distortionCaps,     FovPort eyeFovIn[]) {  EyeRenderDesc eyeRenderDescs[] =     (EyeRenderDesc[]) new EyeRenderDesc().toArray(2);  if (0 == OvrLibrary.INSTANCE.ovrHmd_ConfigureRendering(      this, apiConfig, distortionCaps,       eyeFovIn, eyeRenderDescs)) {    throw new IllegalStateException(      "Unable to configure rendering");  }  configuredRendering = true;  return eyeRenderDescs;}
The end user no longer needs to allocate the EyeRenderDesc array and pass it in.  The wrapper function handles this and simply returns the results, or throws an exception in the case of failure.

Finally, in order to make the API more completely accessible through the Hmd class alone, I created static methods in the Hmd type to wrap the corresponding methods in the OvrLibrary interface.