|
@@ -8,10 +8,24 @@ package com.jme3.input.vr;
|
|
|
import com.jme3.app.VREnvironment;
|
|
|
import com.jme3.math.*;
|
|
|
import com.jme3.renderer.Camera;
|
|
|
+import com.jme3.util.VRUtil;
|
|
|
+
|
|
|
+import java.nio.IntBuffer;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+import java.util.logging.Level;
|
|
|
import java.util.logging.Logger;
|
|
|
|
|
|
+import static org.lwjgl.opengl.GL11.*;
|
|
|
+import static org.lwjgl.ovr.OVR.*;
|
|
|
+
|
|
|
+import static org.lwjgl.ovr.OVRErrorCode.*;
|
|
|
+import static org.lwjgl.system.MemoryUtil.*;
|
|
|
import static org.lwjgl.ovr.OVRUtil.ovr_Detect;
|
|
|
|
|
|
+import org.lwjgl.BufferUtils;
|
|
|
+import org.lwjgl.PointerBuffer;
|
|
|
+
|
|
|
import org.lwjgl.ovr.*;
|
|
|
|
|
|
/**
|
|
@@ -26,6 +40,57 @@ public class OculusVR implements VRAPI {
|
|
|
private final VREnvironment environment;
|
|
|
private boolean initialized;
|
|
|
|
|
|
+ /**
|
|
|
+ * Pointer to the HMD object
|
|
|
+ */
|
|
|
+ private long session;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Information about the VR session (should the app quit, is
|
|
|
+ * it visible or is the universal menu open, etc)
|
|
|
+ */
|
|
|
+ private OVRSessionStatus sessionStatus;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * HMD information, such as product name and manufacturer.
|
|
|
+ */
|
|
|
+ private OVRHmdDesc hmdDesc;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The horizontal resolution of the HMD
|
|
|
+ */
|
|
|
+ private int resolutionW;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The vertical resolution of the HMD
|
|
|
+ */
|
|
|
+ private int resolutionH;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Field-of-view data for each eye (how many degrees from the
|
|
|
+ * center can the user see).
|
|
|
+ */
|
|
|
+ private final OVRFovPort fovPorts[] = new OVRFovPort[2];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Data about each eye to be rendered - in particular, the
|
|
|
+ * offset from the center of the HMD to the eye.
|
|
|
+ */
|
|
|
+ private final OVREyeRenderDesc eyeRenderDesc[] = new OVREyeRenderDesc[2];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Store the projections for each eye, so we don't have to malloc
|
|
|
+ * and recalculate them each frame.
|
|
|
+ */
|
|
|
+ private final OVRMatrix4f[] projections = new OVRMatrix4f[2];
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Store the poses for each eye.
|
|
|
+ *
|
|
|
+ * @see #getHMDMatrixPoseLeftEye()
|
|
|
+ */
|
|
|
+ private final Matrix4f[] eyePoses = new Matrix4f[2];
|
|
|
+
|
|
|
public OculusVR(VREnvironment environment) {
|
|
|
this.environment = environment;
|
|
|
}
|
|
@@ -42,23 +107,117 @@ public class OculusVR implements VRAPI {
|
|
|
|
|
|
@Override
|
|
|
public int getDisplayFrequency() {
|
|
|
- throw new UnsupportedOperationException();
|
|
|
+ // TODO find correct frequency. I'm not sure
|
|
|
+ // if LibOVR has a way to do that, though.
|
|
|
+ return 60;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean initialize() {
|
|
|
OVRDetectResult detect = OVRDetectResult.calloc();
|
|
|
ovr_Detect(0, detect);
|
|
|
- System.out.println("OVRDetectResult.IsOculusHMDConnected = " + detect.IsOculusHMDConnected());
|
|
|
- System.out.println("OVRDetectResult.IsOculusServiceRunning = " + detect.IsOculusServiceRunning());
|
|
|
+ boolean connected = detect.IsOculusHMDConnected();
|
|
|
+ LOGGER.info("OVRDetectResult.IsOculusHMDConnected = " + connected);
|
|
|
+ LOGGER.info("OVRDetectResult.IsOculusServiceRunning = " + detect.IsOculusServiceRunning());
|
|
|
detect.free();
|
|
|
- if (detect.IsOculusHMDConnected() == false) {
|
|
|
+
|
|
|
+ if (!connected) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
initialized = true;
|
|
|
|
|
|
- throw new UnsupportedOperationException("Not yet implemented!");
|
|
|
+ // step 1 - hmd init
|
|
|
+ System.out.println("step 1 - hmd init");
|
|
|
+ OVRLogCallback callback = new OVRLogCallback() {
|
|
|
+ @Override
|
|
|
+ public void invoke(long userData, int level, long message) {
|
|
|
+ System.out.println("LibOVR [" + userData + "] [" + level + "] " + memASCII(message));
|
|
|
+ }
|
|
|
+ };
|
|
|
+ OVRInitParams initParams = OVRInitParams.calloc();
|
|
|
+ initParams.LogCallback(callback);
|
|
|
+ //initParams.Flags(ovrInit_Debug);
|
|
|
+ if (ovr_Initialize(initParams) != ovrSuccess) {
|
|
|
+ System.out.println("init failed");
|
|
|
+ }
|
|
|
+ System.out.println("OVR SDK " + ovr_GetVersionString());
|
|
|
+ initParams.free();
|
|
|
+
|
|
|
+ // step 2 - hmd create
|
|
|
+ System.out.println("step 2 - hmd create");
|
|
|
+ PointerBuffer pHmd = memAllocPointer(1);
|
|
|
+ OVRGraphicsLuid luid = OVRGraphicsLuid.calloc();
|
|
|
+ if (ovr_Create(pHmd, luid) != ovrSuccess) {
|
|
|
+ System.out.println("create failed, try debug");
|
|
|
+ //debug headset is now enabled via the Oculus Configuration util . tools -> Service -> Configure
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ session = pHmd.get(0);
|
|
|
+ memFree(pHmd);
|
|
|
+ luid.free();
|
|
|
+ sessionStatus = OVRSessionStatus.calloc();
|
|
|
+
|
|
|
+ // step 3 - hmdDesc queries
|
|
|
+ System.out.println("step 3 - hmdDesc queries");
|
|
|
+ hmdDesc = OVRHmdDesc.malloc();
|
|
|
+ ovr_GetHmdDesc(session, hmdDesc);
|
|
|
+ System.out.println("ovr_GetHmdDesc = " + hmdDesc.ManufacturerString() + " " + hmdDesc.ProductNameString() + " " + hmdDesc.SerialNumberString() + " " + hmdDesc.Type());
|
|
|
+ if (hmdDesc.Type() == ovrHmd_None) {
|
|
|
+ System.out.println("missing init");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ resolutionW = hmdDesc.Resolution().w();
|
|
|
+ resolutionH = hmdDesc.Resolution().h();
|
|
|
+ System.out.println("resolution W=" + resolutionW + ", H=" + resolutionH);
|
|
|
+ if (resolutionW == 0) {
|
|
|
+ System.out.println("Huh - width=0");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // FOV
|
|
|
+ for (int eye = 0; eye < 2; eye++) {
|
|
|
+ fovPorts[eye] = hmdDesc.DefaultEyeFov(eye);
|
|
|
+ System.out.println("eye " + eye + " = " + fovPorts[eye].UpTan() + ", " + fovPorts[eye].DownTan() + ", " + fovPorts[eye].LeftTan() + ", " + fovPorts[eye].RightTan());
|
|
|
+ }
|
|
|
+ // TODO what does this do? I think it might be the height of the player, for correct floor heights?
|
|
|
+ // playerEyePos = new Vector3f(0.0f, -ovr_GetFloat(session, OVR_KEY_EYE_HEIGHT, 1.65f), 0.0f);
|
|
|
+
|
|
|
+ // step 4 - tracking - no longer needed as of 0.8.0.0
|
|
|
+
|
|
|
+ // step 5 - projections
|
|
|
+ System.out.println("step 5 - projections");
|
|
|
+ for (int eye = 0; eye < 2; eye++) {
|
|
|
+ projections[eye] = OVRMatrix4f.malloc();
|
|
|
+ OVRUtil.ovrMatrix4f_Projection(fovPorts[eye], 0.5f, 500f, OVRUtil.ovrProjection_None, projections[eye]);
|
|
|
+ //1.3 was right handed, now none flag
|
|
|
+ }
|
|
|
+
|
|
|
+ // step 6 - render desc
|
|
|
+ System.out.println("step 6 - render desc");
|
|
|
+ for (int eye = 0; eye < 2; eye++) {
|
|
|
+ eyeRenderDesc[eye] = OVREyeRenderDesc.malloc();
|
|
|
+ ovr_GetRenderDesc(session, eye, fovPorts[eye], eyeRenderDesc[eye]);
|
|
|
+
|
|
|
+ // Changed from an offset to a pose, so there is also a rotation.
|
|
|
+ System.out.println("ipd eye " + eye + " = " + eyeRenderDesc[eye].HmdToEyePose().Position().x());
|
|
|
+
|
|
|
+ OVRPosef pose = eyeRenderDesc[eye].HmdToEyePose();
|
|
|
+
|
|
|
+ Matrix4f jPose = new Matrix4f();
|
|
|
+ jPose.setTranslation(vecO2J(pose.Position(), new Vector3f()));
|
|
|
+ jPose.setRotationQuaternion(quatO2J(pose.Orientation(), new Quaternion()));
|
|
|
+
|
|
|
+ eyePoses[eye] = jPose;
|
|
|
+ }
|
|
|
+
|
|
|
+ // step 7 - recenter
|
|
|
+ System.out.println("step 7 - recenter");
|
|
|
+ ovr_RecenterTrackingOrigin(session);
|
|
|
+
|
|
|
+ // throw new UnsupportedOperationException("Not yet implemented!");
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -68,14 +227,18 @@ public class OculusVR implements VRAPI {
|
|
|
|
|
|
@Override
|
|
|
public void destroy() {
|
|
|
+ throw new UnsupportedOperationException();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void reset() {
|
|
|
+ throw new UnsupportedOperationException();
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void getRenderSize(Vector2f store) {
|
|
|
+ store.x = resolutionW;
|
|
|
+ store.y = resolutionH;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -105,12 +268,12 @@ public class OculusVR implements VRAPI {
|
|
|
|
|
|
@Override
|
|
|
public Matrix4f getHMDMatrixProjectionLeftEye(Camera cam) {
|
|
|
- throw new UnsupportedOperationException();
|
|
|
+ return matrixO2J(projections[ovrEye_Left], new Matrix4f());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public Matrix4f getHMDMatrixProjectionRightEye(Camera cam) {
|
|
|
- throw new UnsupportedOperationException();
|
|
|
+ return matrixO2J(projections[ovrEye_Right], new Matrix4f());
|
|
|
}
|
|
|
|
|
|
@Override
|
|
@@ -130,19 +293,28 @@ public class OculusVR implements VRAPI {
|
|
|
|
|
|
@Override
|
|
|
public Matrix4f getHMDMatrixPoseLeftEye() {
|
|
|
- throw new UnsupportedOperationException();
|
|
|
+ return eyePoses[ovrEye_Left];
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public HmdType getType() {
|
|
|
- throw new UnsupportedOperationException();
|
|
|
+ public Matrix4f getHMDMatrixPoseRightEye() {
|
|
|
+ return eyePoses[ovrEye_Left];
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public Matrix4f getHMDMatrixPoseRightEye() {
|
|
|
+ public HmdType getType() {
|
|
|
throw new UnsupportedOperationException();
|
|
|
}
|
|
|
|
|
|
+ public boolean initVRCompositor(boolean set) {
|
|
|
+ if (!set) {
|
|
|
+ throw new UnsupportedOperationException("Cannot use LibOVR without compositor!");
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO move initialization code here from VRViewManagerOculus
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
public void printLatencyInfoToConsole(boolean set) {
|
|
|
throw new UnsupportedOperationException("Not yet implemented!");
|
|
|
}
|
|
@@ -159,7 +331,62 @@ public class OculusVR implements VRAPI {
|
|
|
throw new UnsupportedOperationException("Not yet implemented!");
|
|
|
}
|
|
|
|
|
|
- public boolean initVRCompositor(boolean set) {
|
|
|
- throw new UnsupportedOperationException("Not yet implemented!");
|
|
|
+ // UTILITIES
|
|
|
+ // TODO move to helper class
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Copy the values from a LibOVR matrix into a jMonkeyEngine matrix.
|
|
|
+ *
|
|
|
+ * @param from The matrix to copy from.
|
|
|
+ * @param to The matrix to copy to.
|
|
|
+ * @return The {@code to} argument.
|
|
|
+ */
|
|
|
+ public static Matrix4f matrixO2J(OVRMatrix4f from, Matrix4f to) {
|
|
|
+ for (int x = 0; x < 4; x++) {
|
|
|
+ for (int y = 0; y < 4; y++) {
|
|
|
+ float val = from.M(x + y * 4); // TODO verify this
|
|
|
+ to.set(x, y, val);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return to;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Copy the values from a LibOVR quaternion into a jMonkeyEngine quaternion.
|
|
|
+ *
|
|
|
+ * @param from The quaternion to copy from.
|
|
|
+ * @param to The quaternion to copy to.
|
|
|
+ * @return The {@code to} argument.
|
|
|
+ */
|
|
|
+ public static Quaternion quatO2J(OVRQuatf from, Quaternion to) {
|
|
|
+ to.set(
|
|
|
+ from.x(),
|
|
|
+ from.y(),
|
|
|
+ from.z(),
|
|
|
+ from.w()
|
|
|
+ );
|
|
|
+
|
|
|
+ return to;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Copy the values from a LibOVR vector into a jMonkeyEngine vector.
|
|
|
+ *
|
|
|
+ * @param from The vector to copy from.
|
|
|
+ * @param to The vector to copy to.
|
|
|
+ * @return The {@code to} argument.
|
|
|
+ */
|
|
|
+ public static Vector3f vecO2J(OVRVector3f from, Vector3f to) {
|
|
|
+ to.set(
|
|
|
+ from.x(),
|
|
|
+ from.y(),
|
|
|
+ from.z()
|
|
|
+ );
|
|
|
+
|
|
|
+ return to;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/* vim: set ts=4 softtabstop=0 sw=4 expandtab: */
|
|
|
+
|