From 2464dcd17add1c097bf1e9224cf798f9e01f2bd1 Mon Sep 17 00:00:00 2001 From: Campbell Suter Date: Fri, 6 Oct 2017 21:56:03 +1300 Subject: [PATCH] Add OculusVRInput --- .../main/java/com/jme3/input/vr/OculusVR.java | 43 +- .../java/com/jme3/input/vr/OculusVRInput.java | 367 ++++++++++++++++++ 2 files changed, 401 insertions(+), 9 deletions(-) create mode 100644 jme3-vr/src/main/java/com/jme3/input/vr/OculusVRInput.java diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/OculusVR.java b/jme3-vr/src/main/java/com/jme3/input/vr/OculusVR.java index 56d49f705..32ba94356 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/OculusVR.java +++ b/jme3-vr/src/main/java/com/jme3/input/vr/OculusVR.java @@ -101,11 +101,21 @@ public class OculusVR implements VRAPI { */ private final Vector3f[] hmdRelativeEyePositions = new Vector3f[2]; + /** + * The current state of the tracked components (HMD, touch) + */ + private OVRTrackingState trackingState; + /** * The position and orientation of the user's head. */ private OVRPosef headPose; + /** + * The state of the Touch controllers. + */ + private OculusVRInput input; + // The size of the texture drawn onto the HMD private int textureW; private int textureH; @@ -129,8 +139,8 @@ public class OculusVR implements VRAPI { } @Override - public OpenVRInput getVRinput() { - throw new UnsupportedOperationException(); + public OculusVRInput getVRinput() { + return input; } @Override @@ -184,7 +194,7 @@ public class OculusVR implements VRAPI { 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; + return false; // TODO fix memory leak - destroy() is not called } session = pHmd.get(0); memFree(pHmd); @@ -198,7 +208,7 @@ public class OculusVR implements VRAPI { 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; + return false; // TODO fix memory leak - destroy() is not called } resolutionW = hmdDesc.Resolution().w(); @@ -206,7 +216,7 @@ public class OculusVR implements VRAPI { System.out.println("resolution W=" + resolutionW + ", H=" + resolutionH); if (resolutionW == 0) { System.out.println("Huh - width=0"); - return false; + return false; // TODO fix memory leak - destroy() is not called } // FOV @@ -254,6 +264,12 @@ public class OculusVR implements VRAPI { // Do this so others relying on our texture size get it correct. findHMDTextureSize(); + // Set up the tracking system + trackingState = OVRTrackingState.malloc(); + + // Set up the input + input = new OculusVRInput(this, session, sessionStatus, trackingState); + // throw new UnsupportedOperationException("Not yet implemented!"); return true; } @@ -261,12 +277,13 @@ public class OculusVR implements VRAPI { @Override public void updatePose() { double ftiming = ovr_GetPredictedDisplayTime(session, 0); - OVRTrackingState hmdState = OVRTrackingState.malloc(); - ovr_GetTrackingState(session, ftiming, true, hmdState); + ovr_GetTrackingState(session, ftiming, true, trackingState); + ovr_GetSessionStatus(session, sessionStatus); + + input.updateControllerStates(); //get head pose - headPose = hmdState.HeadPose().ThePose(); - hmdState.free(); + headPose = trackingState.HeadPose().ThePose(); } @Override @@ -278,6 +295,9 @@ public class OculusVR implements VRAPI { public void destroy() { // fovPorts: contents are managed by LibOVR, no need to do anything. + // Clean up the input + input.dispose(); + // Check if we've set up rendering - if so, clean that up. if (chains != null) { // Destroy our set of huge buffer images. @@ -299,6 +319,7 @@ public class OculusVR implements VRAPI { } hmdDesc.free(); + trackingState.free(); sessionStatus.free(); // Wrap everything up @@ -636,6 +657,10 @@ public class OculusVR implements VRAPI { public OVRPosef getEyePose(int eye) { return eyeRenderDesc[eye].HmdToEyePose(); } + + public VREnvironment getEnvironment() { + return environment; + } } /* vim: set ts=4 softtabstop=0 sw=4 expandtab: */ diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/OculusVRInput.java b/jme3-vr/src/main/java/com/jme3/input/vr/OculusVRInput.java new file mode 100644 index 000000000..9016850bc --- /dev/null +++ b/jme3-vr/src/main/java/com/jme3/input/vr/OculusVRInput.java @@ -0,0 +1,367 @@ +package com.jme3.input.vr; + +import com.jme3.app.VREnvironment; +import com.jme3.math.*; +import com.jme3.renderer.Camera; +import com.jme3.scene.Spatial; +import com.jme3.util.VRViewManagerOculus; +import org.lwjgl.ovr.*; + +import static org.lwjgl.ovr.OVR.*; + +public class OculusVRInput implements VRInputAPI { + // State control + private final OVRInputState inputState; + private final OVRSessionStatus sessionStatus; + private final OVRTrackingState trackingState; + private final OculusVR hardware; + private long session; + + // Setup values + private float axisMultiplier = 1; + + // Cached stuff + private int buttons, touch; + + // Used to calculate sinceLastCall stuff + private int lastButtons, lastTouch; + private final Vector2f[][] lastAxises; + + /** + * The state data (linear and angular velocity and acceleration) for each hand + */ + private OVRPoseStatef[] handStates; + + /** + * The position and orientation of the Touch controllers. + */ + private OVRPosef[] handPoses; + + /** + * The object forms of the tracked controllers. + */ + private final OculusController[] controllers = { + new OculusController(0), + new OculusController(1) + }; + + public OculusVRInput(OculusVR hardware, long session, + OVRSessionStatus sessionStatus, OVRTrackingState trackingState) { + this.hardware = hardware; + this.session = session; + this.sessionStatus = sessionStatus; + this.trackingState = trackingState; + + inputState = OVRInputState.calloc(); + + handStates = new OVRPoseStatef[ovrHand_Count]; + handPoses = new OVRPosef[handStates.length]; + lastAxises = new Vector2f[handStates.length][3]; // trigger+grab+thumbstick for each hand. + } + + public void dispose() { + inputState.free(); + session = 0; // Crashing > undefined behaviour if this object is incorrectly accessed again. + } + + @Override + public void updateControllerStates() { + // Handle buttons, axies + ovr_GetInputState(session, ovrControllerType_Touch, inputState); + buttons = inputState.Buttons(); + touch = inputState.Touches(); + + // Get the touch controller poses + // TODO what if no touch controllers are available? + for (int hand = 0; hand < handPoses.length; hand++) { + handStates[hand] = trackingState.HandPoses(hand); + handPoses[hand] = handStates[hand].ThePose(); + } + } + + private Vector3f cv(OVRVector3f in) { + // TODO do we want to reuse vectors rather than making new ones? + // TODO OpenVRInput does this, but it will probably cause some bugs. + return OculusVR.vecO2J(in, new Vector3f()); // This also fixes the coordinate space transform issues. + } + + private Vector2f cv(OVRVector2f in) { + // TODO do we want to reuse vectors rather than making new ones? + // TODO OpenVRInput does this, but it will probably cause some bugs. + return new Vector2f(in.x(), in.y()); + } + + private Quaternion cq(OVRQuatf in) { + // TODO do we want to reuse quaternions rather than making new ones? + // TODO OpenVRInput does this, but it will probably cause some bugs. + return OculusVR.quatO2J(in, new Quaternion()); // This also fixes the coordinate space transform issues. + } + + private Vector2f axis(float input) { + // See above comments about reusing vectors + return new Vector2f(input, input); + } + + // Tracking (position, rotation, velocity, status) + + @Override + public Vector3f getPosition(int index) { + return cv(handPoses[index].Position()); + } + + @Override + public Vector3f getVelocity(int controllerIndex) { + return cv(handStates[controllerIndex].LinearVelocity()); + } + + @Override + public Quaternion getOrientation(int index) { + return cq(handPoses[index].Orientation()); + } + + @Override + public Vector3f getAngularVelocity(int controllerIndex) { + return cv(handStates[controllerIndex].AngularVelocity()); + } + + @Override + public Quaternion getFinalObserverRotation(int index) { + // Copied from OpenVRInput + + VREnvironment env = hardware.getEnvironment(); + VRViewManagerOculus vrvm = (VRViewManagerOculus) hardware.getEnvironment().getVRViewManager(); + + Object obs = env.getObserver(); + Quaternion tempq = new Quaternion(); // TODO move to class scope? + if (obs instanceof Camera) { + tempq.set(((Camera) obs).getRotation()); + } else { + tempq.set(((Spatial) obs).getWorldRotation()); + } + + return tempq.multLocal(getOrientation(index)); + } + + @Override + public Vector3f getFinalObserverPosition(int index) { + // Copied from OpenVRInput + + VREnvironment env = hardware.getEnvironment(); + VRViewManagerOculus vrvm = (VRViewManagerOculus) hardware.getEnvironment().getVRViewManager(); + + Object obs = env.getObserver(); + Vector3f pos = getPosition(index); + if (obs instanceof Camera) { + ((Camera) obs).getRotation().mult(pos, pos); + return pos.addLocal(((Camera) obs).getLocation()); + } else { + ((Spatial) obs).getWorldRotation().mult(pos, pos); + return pos.addLocal(((Spatial) obs).getWorldTranslation()); + } + } + + @Override + public boolean isInputDeviceTracking(int index) { + int flags = trackingState.HandStatusFlags(index); + return (flags & ovrStatus_PositionTracked) != 0; // TODO do we require orientation as well? + } + + // Input Getters + + @Override + public Vector2f getAxis(int controllerIndex, VRInputType forAxis) { + Vector2f result = getAxisRaw(controllerIndex, forAxis); + return result == null ? null : result.multLocal(axisMultiplier); + } + + @Override + public Vector2f getAxisRaw(int controllerIndex, VRInputType forAxis) { + switch (forAxis) { + case OculusThumbstickAxis: + return cv(inputState.Thumbstick(controllerIndex)); + case OculusTriggerAxis: + return axis(inputState.IndexTrigger(controllerIndex)); + case OculusGripAxis: + return axis(inputState.HandTrigger(controllerIndex)); + default: + return null; + } + } + + @Override + public boolean isButtonDown(int controllerIndex, VRInputType checkButton) { + return isButtonDownForStatus(controllerIndex, checkButton, buttons, touch); + } + + public boolean isButtonDownForStatus(int controllerIndex, VRInputType checkButton, int buttons, int touch) { + int buttonMask = (controllerIndex == ovrHand_Left) ? ovrButton_LMask : ovrButton_RMask; + int touchMask = (controllerIndex == ovrHand_Left) ? + (ovrTouch_LButtonMask + ovrTouch_LPoseMask) : + (ovrTouch_RButtonMask + ovrTouch_RPoseMask); + + switch (checkButton) { + default: + return false; + + case OculusTopButton: // Physical buttons + case OculusBottomButton: + case OculusThumbstickButton: + case OculusMenuButton: + return (buttons & buttonMask & checkButton.getValue()) != 0; + + case OculusTopTouch: // Standard capacitive buttons + case OculusBottomTouch: + case OculusThumbstickTouch: + case OculusThumbrestTouch: + case OculusIndexTouch: + case OculusThumbUp: // Calculated/virtual capacitive buttons + case OculusIndexPointing: + return (touch & touchMask & checkButton.getValue()) != 0; + } + } + + // Since-last-call stuff + + @Override + public void resetInputSinceLastCall() { + lastButtons = 0; + lastTouch = 0; + } + + @Override + public boolean wasButtonPressedSinceLastCall(int controllerIndex, VRInputType checkButton) { + boolean wasPressed = isButtonDownForStatus(controllerIndex, checkButton, lastButtons, lastTouch); + lastButtons = buttons; + lastTouch = touch; + return !wasPressed && isButtonDown(controllerIndex, checkButton); + } + + @Override + public Vector2f getAxisDeltaSinceLastCall(int controllerIndex, VRInputType forAxis) { + int index; + switch (forAxis) { + case OculusTriggerAxis: + index = 0; + break; + case OculusGripAxis: + index = 1; + break; + case OculusThumbstickAxis: + index = 2; + break; + default: + return null; + } + + Vector2f last = lastAxises[controllerIndex][index]; + if (last == null) { + last = lastAxises[controllerIndex][index] = new Vector2f(); + } + + Vector2f current = getAxis(controllerIndex, forAxis); + + // TODO could this lead to accuracy problems? + current.subtractLocal(last); + last.addLocal(current); + + return current; + } + + // Misc + + @Override + public boolean init() { + throw new UnsupportedOperationException("Input initialized at creation time"); + } + + @Override + public void updateConnectedControllers() { + throw new UnsupportedOperationException("Automatically done by LibOVR (I think?)"); + } + + @Override + public float getAxisMultiplier() { + return axisMultiplier; + } + + @Override + public void setAxisMultiplier(float axisMultiplier) { + this.axisMultiplier = axisMultiplier; + } + + @Override + public void triggerHapticPulse(int controllerIndex, float seconds) { + // TODO: How do we time so we can turn the feedback off? + } + + @Override + public boolean isInputFocused() { + return sessionStatus.IsVisible(); // TODO do we need HmdMounted, or is it counted in IsVisible + } + + @Override + public Object getRawControllerState(int index) { + throw new UnsupportedOperationException("Cannot get raw controller state!"); + } + + @Override + public void swapHands() { + // Do nothing. + // TODO although OSVR and OpenVR if it has more than two controllers both do nothing, shouldn't we be + // TODO throwing an exception or something? + } + + @Override + public int getTrackedControllerCount() { + // TODO: Shouldn't we be seeing if the user has the touch controllers first? + return 2; + } + + @Override + public VRTrackedController getTrackedController(int index) { + return controllers[index]; + } + + /** + * The object form representation of a controller. + */ + public class OculusController implements VRTrackedController { + + /** + * The ID of the hand to track + */ + private int hand; + + public OculusController(int hand) { + this.hand = hand; + } + + @Override + public String getControllerName() { + return "Touch"; // TODO + } + + @Override + public String getControllerManufacturer() { + return "Oculus"; // TODO + } + + @Override + public Vector3f getPosition() { + return OculusVRInput.this.getPosition(hand); + } + + @Override + public Quaternion getOrientation() { + return OculusVRInput.this.getOrientation(hand); + } + + @Override + public Matrix4f getPose() { + Matrix4f mat = new Matrix4f(); + mat.setRotationQuaternion(getOrientation()); + mat.setTranslation(getPosition()); + return mat; + } + } +}