torsdag 9 maj 2013

Java C++ wrapper for Oculus Rift

I've wanted a VR helmet ever since i tried one for the first time in the early 90s. Looks like we're finally getting somewhere close to a commercial variant! Even if we don't, I'll be happy with the devkit I've ordered.Now, I'm a java developer, and thus I'm not too comfortable developing with the C++ SDK. Given that I have plenty of time before it arrives, I decided to make a wrapper, so I could continue to use jMonkeyEngine, which I'm more proficient with.

The wrapper itself is not dependent on jME in any way. To build the C++ part, i recommend following the "Minimal Oculus Application" tutorial in the developer section of the Oculus web site.

Let's start with the java side.

HMDInfo.java
This is pretty much a direct mapping of the HMD(Head Mounted Display) data, straight from the C++ code, hence the naming convention is not 100% java.


 package oculusvr.input;  
 public class HMDInfo {  
      protected int HResolution;  
      protected int VResolution;  
      protected float HScreenSize;  
      protected float VScreenSize;  
      protected float VScreenCenter;  
      protected float EyeToScreenDistance;  
      protected float LensSeparationDistance;  
      protected float InterpupillaryDistance;  
      protected float[] DistortionK = new float[4];  
      protected int DesktopX;  
      protected int DesktopY;  
      protected String DisplayDeviceName = "";  
      protected long DisplayId = 0;  
   public int getHResolution() {  
     return HResolution;  
   }  
   public void setHResolution(int HResolution) {  
     this.HResolution = HResolution;  
   }  
   public int getVResolution() {  
     return VResolution;  
   }  
   public void setVResolution(int VResolution) {  
     this.VResolution = VResolution;  
   }  
   public float getHScreenSize() {  
     return HScreenSize;  
   }  
   public void setHScreenSize(float HScreenSize) {  
     this.HScreenSize = HScreenSize;  
   }  
   public float getVScreenCenter() {  
     return VScreenCenter;  
   }  
   public void setVScreenCenter(float VScreenCenter) {  
     this.VScreenCenter = VScreenCenter;  
   }  
   public float getEyeToScreenDistance() {  
     return EyeToScreenDistance;  
   }  
   public void setEyeToScreenDistance(float EyeToScreenDistance) {  
     this.EyeToScreenDistance = EyeToScreenDistance;  
   }  
   public float getLensSeperationDistance() {  
     return LensSeparationDistance;  
   }  
   public void setLensSeperationDistance(float LensSeperationDistance) {  
     this.LensSeparationDistance = LensSeperationDistance;  
   }  
   public float getInterpupillaryDistance() {  
     return InterpupillaryDistance;  
   }  
   public void setInterpupillaryDistance(float InterpupillaryDistance) {  
     this.InterpupillaryDistance = InterpupillaryDistance;  
   }  
   public float[] getDistortionK() {  
     return DistortionK;  
   }  
   public void setDistortionK(float[] DistortionK) {  
     this.DistortionK = DistortionK;  
   }  
   public int getDesktopX() {  
     return DesktopX;  
   }  
   public void setDesktopX(int DesktopX) {  
     this.DesktopX = DesktopX;  
   }  
   public int getDesktopY() {  
     return DesktopY;  
   }  
   public void setDesktopY(int DesktopY) {  
     this.DesktopY = DesktopY;  
   }  
   public String getDisplayDeviceName() {  
     return DisplayDeviceName;  
   }  
   public void setDisplayDeviceName(String DisplayDeviceName) {  
     this.DisplayDeviceName = DisplayDeviceName;  
   }  
   public long getDisplayId() {  
     return DisplayId;  
   }  
   public void setDisplayId(long DisplayId) {  
     this.DisplayId = DisplayId;  
   }  
   @Override  
   public String toString() {  
       return "HMDInfo [HResolution = " + HResolution +   
           ", VResolution = " + VResolution +   
           ", HScreenSize = " + HScreenSize +   
           ", VScreenSize = " + VScreenSize +   
           ", VScreenCenter = " + VScreenCenter +   
           ", EyeToScreenDistance = " + EyeToScreenDistance +  
           ", LensSeperationDistance = " + LensSeparationDistance +   
           ", InterpupillaryDistance = " + InterpupillaryDistance +   
           ", DistortionK = [" + DistortionK[0] + ", " + DistortionK[1] + ", " + DistortionK[2] + ", " + DistortionK[3] + "] " +   
           ", DesktopX = " + DesktopX +   
           ", DesktopY = " + DesktopY +   
           ", DisplayDeviceName = " + DisplayDeviceName +   
           ", DisplayId = " + DisplayId + "]";  
   }  
 } 

OculusRift.java
The wrapper on the java side. Should be pretty self-explanatory with initialization and getter methods. One note, due to the overhead involved in calling native methods, I decided to grab all the rotation and acceleration data in one go (update()).
Please note that you might need to change the lib names depending on how you name your C++ project.

 /*  
  * To change this template, choose Tools | Templates  
  * and open the template in the editor.  
  */  
 package oculusvr.input;  
 /**  
  *  
  * @author Rickard  
  */  
 public class OculusRift {  
   static {  
     if(System.getProperty("sun.arch.data.model").equals("32")){  
       System.loadLibrary("OculusLib");  
     } else if (System.getProperty("sun.arch.data.model").equals("64")){  
       System.loadLibrary("OculusLib64");  
     }  
   }  
   public native boolean initialize();  
   /**  
    *   
    * @return array of [roll, pitch, yaw, acc x, acc y, acc z]  
    */  
   public native float[] update();  
   // HMDInfo  
   public native int getHResolution();  
   public native int getVResolution();  
   public native float getHScreenSize();  
   public native float getVScreenSize();  
   public native float getVScreenCenter();  
   public native float getEyeToScreenDistance();  
   public native float getLensSeparationDistance();  
   public native float getInterpupillaryDistance();  
   public native float[] getDistortionK();  
   public native int getDesktopX();  
   public native int getDesktopY();  
   public native String getDisplayDeviceName();  
   public native long getDisplayId();  
   public native void destroy();  
 }  




That's all the java that's needed. To the C++ code!

OculusRift.h
The much needed header file. Generated with javah:

 /* DO NOT EDIT THIS FILE - it is machine generated */  
 #include <jni.h>  
 /* Header for class oculusvr_input_OculusRift */  
 #ifndef _Included_oculusvr_input_OculusRift  
 #define _Included_oculusvr_input_OculusRift  
 #ifdef __cplusplus  
 extern "C" {  
 #endif  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  initialize  
  * Signature: ()Z  
  */  
 JNIEXPORT jboolean JNICALL Java_oculusvr_input_OculusRift_initialize  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  update  
  * Signature: ()[F  
  */  
 JNIEXPORT jfloatArray JNICALL Java_oculusvr_input_OculusRift_update  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getHResolution  
  * Signature: ()I  
  */  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getHResolution  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getVResolution  
  * Signature: ()I  
  */  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getVResolution  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getHScreenSize  
  * Signature: ()F  
  */  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getHScreenSize  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getVScreenSize  
  * Signature: ()F  
  */  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getVScreenSize  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getVScreenCenter  
  * Signature: ()F  
  */  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getVScreenCenter  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getEyeToScreenDistance  
  * Signature: ()F  
  */  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getEyeToScreenDistance  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getLensSeparationDistance  
  * Signature: ()F  
  */  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getLensSeparationDistance  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getInterpupillaryDistance  
  * Signature: ()F  
  */  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getInterpupillaryDistance  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getDistortionK  
  * Signature: ()[F  
  */  
 JNIEXPORT jfloatArray JNICALL Java_oculusvr_input_OculusRift_getDistortionK  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getDesktopX  
  * Signature: ()I  
  */  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getDesktopX  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getDesktopY  
  * Signature: ()I  
  */  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getDesktopY  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getDisplayDeviceName  
  * Signature: ()Ljava/lang/String;  
  */  
 JNIEXPORT jstring JNICALL Java_oculusvr_input_OculusRift_getDisplayDeviceName  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  getDisplayId  
  * Signature: ()J  
  */  
 JNIEXPORT jlong JNICALL Java_oculusvr_input_OculusRift_getDisplayId  
  (JNIEnv *, jobject);  
 /*  
  * Class:   oculusvr_input_OculusRift  
  * Method:  destroy  
  * Signature: ()V  
  */  
 JNIEXPORT void JNICALL Java_oculusvr_input_OculusRift_destroy  
  (JNIEnv *, jobject);  
 #ifdef __cplusplus  
 }  
 #endif  
 #endif  

OculusRift.cpp.
This is pretty much an adaptation of the "Minimal oculus application" found on the oculus site.

 #include <jni.h>  
 #include <iostream>  
 #include <stdio.h>  
 #include "OVR.h"  
 #include "OculusRift.h"  
 using namespace OVR;  
 using namespace std;  
 Ptr<DeviceManager>     pManager;  
 Ptr<HMDDevice>     pHMD;  
 Ptr<SensorDevice>     pSensor;  
 SensorFusion     FusionResult;  
 HMDInfo     Info;  
 bool InfoLoaded;  
 //#define STD_GRAV 9.81 // What SHOULD work with Rift, but off by 1000  
 #define STD_GRAV 0.00981 // This gives nice 1.00G on Z with Rift face down !!!  
 JNIEXPORT jboolean JNICALL Java_oculusvr_input_OculusRift_initialize  
  (JNIEnv *env, jobject thisObj) {  
   printf("Initializing Rift...\n");  
   System::Init();  
   bool initialized = false;  
      pManager = *DeviceManager::Create();  
      pHMD = *pManager->EnumerateDevices<HMDDevice>().CreateDevice();  
      if (pHMD){  
           printf("pHMD created\n");  
           InfoLoaded = pHMD->GetDeviceInfo(&Info);  
           pSensor = *pHMD->GetSensor();  
      }  
      else{  
           pSensor = *pManager->EnumerateDevices<SensorDevice>().CreateDevice();  
      }  
      if (pSensor)  
      {  
           printf("Attaching sensor\n");  
           FusionResult.AttachToSensor(pSensor);  
     initialized = true;  
      }  
   return initialized;  
 }  
 JNIEXPORT void JNICALL Java_oculusvr_input_OculusRift_destroyDevice  
      (JNIEnv *env, jobject thisObj) {  
           pSensor.Clear();  
     pHMD.Clear();  
           pManager.Clear();  
           System::Destroy();  
           return;  
 }  
 JNIEXPORT jfloatArray JNICALL Java_oculusvr_input_OculusRift_update  
  (JNIEnv *env, jobject thisObj) {  
   jfloat data[6];  
   Vector3f acc=FusionResult.GetAcceleration();  
   // yaw, pitch, roll  
   FusionResult.GetOrientation().GetEulerAngles<Axis_Y, Axis_X, Axis_Z>(&data[2], &data[1], &data[0]);  
   data[3] = acc.x / STD_GRAV;  
   data[4] = acc.y / STD_GRAV;  
   data[5] = acc.z / STD_GRAV;  
   jfloatArray result;  
   result = env->NewFloatArray(6);  
   if (result == NULL) {  
     return NULL; /* out of memory error thrown */  
   }  
   env->SetFloatArrayRegion(result, 0, 6, data);  
   return result;  
 }  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getHResolution  
  (JNIEnv *env, jobject thisObj) {  
   return Info.HResolution;  
 }  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getVResolution  
  (JNIEnv *env, jobject thisObj) {  
   return Info.VResolution;  
 }  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getHScreenSize  
  (JNIEnv *env, jobject thisObj) {  
   return Info.HScreenSize;  
 }  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getVScreenSize  
  (JNIEnv *env, jobject thisObj) {  
   return Info.VScreenSize;  
 }  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getVScreenCenter  
  (JNIEnv *env, jobject thisObj) {  
   return Info.VScreenCenter;  
 }  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getEyeToScreenDistance  
  (JNIEnv *env, jobject thisObj) {  
   return Info.EyeToScreenDistance;  
 }  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getLensSeparationDistance  
  (JNIEnv *env, jobject thisObj) {  
   return Info.LensSeparationDistance;  
 }  
 JNIEXPORT jfloat JNICALL Java_oculusvr_input_OculusRift_getInterpupillaryDistance  
  (JNIEnv *env, jobject thisObj) {  
   return Info.InterpupillaryDistance;  
 }  
 JNIEXPORT jfloatArray JNICALL Java_oculusvr_input_OculusRift_getDistortionK  
  (JNIEnv *env, jobject thisObj) {  
   jfloat* distortion = Info.DistortionK;  
   jfloatArray result;  
   result = env->NewFloatArray(6);  
   if (result == NULL) {  
     return NULL; /* out of memory error thrown */  
   }  
   env->SetFloatArrayRegion(result, 0, 6, distortion);  
   return result;  
 }  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getDesktopX  
  (JNIEnv *env, jobject thisObj) {  
   return Info.DesktopX;  
 }  
 JNIEXPORT jint JNICALL Java_oculusvr_input_OculusRift_getDesktopY  
  (JNIEnv *env, jobject thisObj) {  
   return Info.DesktopY;  
 }  
 JNIEXPORT jstring JNICALL Java_oculusvr_input_OculusRift_getDisplayDeviceName  
  (JNIEnv *env, jobject thisObj) {  
   char *name = Info.DisplayDeviceName;  
   jstring result = env->NewStringUTF(name);  
   return result;  
 }  
 JNIEXPORT jlong JNICALL Java_oculusvr_input_OculusRift_getDisplayId  
  (JNIEnv *env, jobject thisObj) {  
   return Info.DisplayId;  
 }  

Edit: Update to the code. Formalized it a bit better