Selaa lähdekoodia

Add OculusVRInput

Campbell Suter 8 vuotta sitten
vanhempi
commit
2464dcd17a

+ 34 - 9
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: */

+ 367 - 0
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;
+        }
+    }
+}