/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package com.jme3.input.vr; import com.jme3.app.VRApplication; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.system.jopenvr.HmdMatrix34_t; import com.jme3.system.jopenvr.HmdMatrix44_t; import com.jme3.system.jopenvr.JOpenVRLibrary; import com.jme3.system.jopenvr.TrackedDevicePose_t; import com.jme3.system.jopenvr.VR_IVRCompositor_FnTable; import com.jme3.system.jopenvr.VR_IVRSystem_FnTable; import com.sun.jna.Memory; import com.sun.jna.Pointer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.LongBuffer; import java.util.Locale; import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import jmevr.util.VRUtil; /** * A class that wraps an OpenVR system. * @author reden - phr00t - https://github.com/phr00t * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org */ public class OpenVR implements VRAPI { private static final Logger logger = Logger.getLogger(OpenVR.class.getName()); private static VR_IVRCompositor_FnTable compositorFunctions; private static VR_IVRSystem_FnTable vrsystemFunctions; private static boolean initSuccess = false; private static boolean flipEyes = false; private static IntBuffer hmdDisplayFrequency; private static TrackedDevicePose_t.ByReference hmdTrackedDevicePoseReference; protected static TrackedDevicePose_t[] hmdTrackedDevicePoses; protected static IntBuffer hmdErrorStore; private static final Quaternion rotStore = new Quaternion(); private static final Vector3f posStore = new Vector3f(); private static FloatBuffer tlastVsync; /** * The actual frame count. */ public static LongBuffer _tframeCount; // for debugging latency private int frames = 0; protected static Matrix4f[] poseMatrices; private static final Matrix4f hmdPose = Matrix4f.IDENTITY.clone(); private static Matrix4f hmdProjectionLeftEye; private static Matrix4f hmdProjectionRightEye; private static Matrix4f hmdPoseLeftEye; private static Matrix4f hmdPoseRightEye; private static Vector3f hmdPoseLeftEyeVec, hmdPoseRightEyeVec, hmdSeatToStand; private static float vsyncToPhotons; private static double timePerFrame, frameCountRun; private static long frameCount; private static OpenVRInput VRinput; private VRApplication application = null; /** * Create a new OpenVR system attached to the given application. * @param application the application to which the input is attached. */ public OpenVR(VRApplication application){ this.application = application; } @Override public OpenVRInput getVRinput() { return VRinput; } @Override public VR_IVRSystem_FnTable getVRSystem() { return vrsystemFunctions; } @Override public VR_IVRCompositor_FnTable getCompositor() { return compositorFunctions; } @Override public String getName() { return "OpenVR"; } private static long latencyWaitTime = 0; @Override public void _setFlipEyes(boolean set) { flipEyes = set; } private boolean enableDebugLatency = false; @Override public void printLatencyInfoToConsole(boolean set) { enableDebugLatency = set; } @Override public int getDisplayFrequency() { if( hmdDisplayFrequency == null ) return 0; return hmdDisplayFrequency.get(0); } @Override public boolean initialize() { logger.config("Initializing OpenVR system."); hmdErrorStore = IntBuffer.allocate(1); vrsystemFunctions = null; JOpenVRLibrary.VR_InitInternal(hmdErrorStore, JOpenVRLibrary.EVRApplicationType.EVRApplicationType_VRApplication_Scene); if( hmdErrorStore.get(0) == 0 ) { // ok, try and get the vrsystem pointer.. vrsystemFunctions = new VR_IVRSystem_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRSystem_Version, hmdErrorStore)); } if( vrsystemFunctions == null || hmdErrorStore.get(0) != 0 ) { System.out.println("OpenVR Initialize Result: " + JOpenVRLibrary.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.get(0)).getString(0)); return false; } else { System.out.println("OpenVR initialized & VR connected."); vrsystemFunctions.setAutoSynch(false); vrsystemFunctions.read(); tlastVsync = FloatBuffer.allocate(1); _tframeCount = LongBuffer.allocate(1); hmdDisplayFrequency = IntBuffer.allocate(1); hmdDisplayFrequency.put( (int) JOpenVRLibrary.ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayFrequency_Float); hmdTrackedDevicePoseReference = new TrackedDevicePose_t.ByReference(); hmdTrackedDevicePoses = (TrackedDevicePose_t[])hmdTrackedDevicePoseReference.toArray(JOpenVRLibrary.k_unMaxTrackedDeviceCount); poseMatrices = new Matrix4f[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; for(int i=0;i 0 ) VRUtil.sleepNanos(latencyWaitTime); vrsystemFunctions.GetTimeSinceLastVsync.apply(tlastVsync, _tframeCount); float fSecondsUntilPhotons = (float)timePerFrame - tlastVsync.get(0) + vsyncToPhotons; if( enableDebugLatency ) { if( frames == 10 ) { System.out.println("Waited (nanos): " + Long.toString(latencyWaitTime)); System.out.println("Predict ahead time: " + Float.toString(fSecondsUntilPhotons)); } frames = (frames + 1) % 60; } // handle skipping frame stuff long nowCount = _tframeCount.get(0); if( nowCount - frameCount > 1 ) { // skipped a frame! if( enableDebugLatency ) System.out.println("Frame skipped!"); frameCountRun = 0; if( latencyWaitTime > 0 ) { latencyWaitTime -= TimeUnit.MILLISECONDS.toNanos(1); if( latencyWaitTime < 0 ) latencyWaitTime = 0; } } else if( latencyWaitTime < timePerFrame * 1000000000.0 ) { // didn't skip a frame, lets try waiting longer to improve latency frameCountRun++; latencyWaitTime += Math.round(Math.pow(frameCountRun / 10.0, 2.0)); } frameCount = nowCount; vrsystemFunctions.GetDeviceToAbsoluteTrackingPose.apply( application.isSeatedExperience()?JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated: JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding, fSecondsUntilPhotons, hmdTrackedDevicePoseReference, JOpenVRLibrary.k_unMaxTrackedDeviceCount); } // deal with controllers being plugged in and out // causing an invalid memory crash... skipping for now /*boolean hasEvent = false; while( JOpenVRLibrary.VR_IVRSystem_PollNextEvent(OpenVR.getVRSystemInstance(), tempEvent) != 0 ) { // wait until the events are clear.. hasEvent = true; } if( hasEvent ) { // an event probably changed controller state VRInput._updateConnectedControllers(); }*/ //update controllers pose information application.getVRinput().updateControllerStates(); // read pose data from native for (int nDevice = 0; nDevice < JOpenVRLibrary.k_unMaxTrackedDeviceCount; ++nDevice ){ hmdTrackedDevicePoses[nDevice].readField("bPoseIsValid"); if( hmdTrackedDevicePoses[nDevice].bPoseIsValid != 0 ){ hmdTrackedDevicePoses[nDevice].readField("mDeviceToAbsoluteTracking"); VRUtil.convertSteamVRMatrix3ToMatrix4f(hmdTrackedDevicePoses[nDevice].mDeviceToAbsoluteTracking, poseMatrices[nDevice]); } } if ( hmdTrackedDevicePoses[JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd].bPoseIsValid != 0 ){ hmdPose.set(poseMatrices[JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd]); } else { hmdPose.set(Matrix4f.IDENTITY); } } @Override public Matrix4f getHMDMatrixProjectionLeftEye(Camera cam){ if( hmdProjectionLeftEye != null ) { return hmdProjectionLeftEye; } else if(vrsystemFunctions == null){ return cam.getProjectionMatrix(); } else { HmdMatrix44_t mat = vrsystemFunctions.GetProjectionMatrix.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, cam.getFrustumNear(), cam.getFrustumFar(), JOpenVRLibrary.EGraphicsAPIConvention.EGraphicsAPIConvention_API_OpenGL); hmdProjectionLeftEye = new Matrix4f(); VRUtil.convertSteamVRMatrix4ToMatrix4f(mat, hmdProjectionLeftEye); return hmdProjectionLeftEye; } } @Override public Matrix4f getHMDMatrixProjectionRightEye(Camera cam){ if( hmdProjectionRightEye != null ) { return hmdProjectionRightEye; } else if(vrsystemFunctions == null){ return cam.getProjectionMatrix(); } else { HmdMatrix44_t mat = vrsystemFunctions.GetProjectionMatrix.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, cam.getFrustumNear(), cam.getFrustumFar(), JOpenVRLibrary.EGraphicsAPIConvention.EGraphicsAPIConvention_API_OpenGL); hmdProjectionRightEye = new Matrix4f(); VRUtil.convertSteamVRMatrix4ToMatrix4f(mat, hmdProjectionRightEye); return hmdProjectionRightEye; } } @Override public Vector3f getHMDVectorPoseLeftEye() { if( hmdPoseLeftEyeVec == null ) { hmdPoseLeftEyeVec = getHMDMatrixPoseLeftEye().toTranslationVector(); // set default IPD if none or broken if( hmdPoseLeftEyeVec.x <= 0.080f * -0.5f || hmdPoseLeftEyeVec.x >= 0.040f * -0.5f ) { hmdPoseLeftEyeVec.x = 0.065f * -0.5f; } if( flipEyes == false ) hmdPoseLeftEyeVec.x *= -1f; // it seems these need flipping } return hmdPoseLeftEyeVec; } @Override public Vector3f getHMDVectorPoseRightEye() { if( hmdPoseRightEyeVec == null ) { hmdPoseRightEyeVec = getHMDMatrixPoseRightEye().toTranslationVector(); // set default IPD if none or broken if( hmdPoseRightEyeVec.x >= 0.080f * 0.5f || hmdPoseRightEyeVec.x <= 0.040f * 0.5f ) { hmdPoseRightEyeVec.x = 0.065f * 0.5f; } if( flipEyes == false ) hmdPoseRightEyeVec.x *= -1f; // it seems these need flipping } return hmdPoseRightEyeVec; } @Override public Vector3f getSeatedToAbsolutePosition() { if( application.isSeatedExperience() == false ) return Vector3f.ZERO; if( hmdSeatToStand == null ) { hmdSeatToStand = new Vector3f(); HmdMatrix34_t mat = vrsystemFunctions.GetSeatedZeroPoseToStandingAbsoluteTrackingPose.apply(); Matrix4f tempmat = new Matrix4f(); VRUtil.convertSteamVRMatrix3ToMatrix4f(mat, tempmat); tempmat.toTranslationVector(hmdSeatToStand); } return hmdSeatToStand; } @Override public Matrix4f getHMDMatrixPoseLeftEye(){ if( hmdPoseLeftEye != null ) { return hmdPoseLeftEye; } else if(vrsystemFunctions == null) { return Matrix4f.IDENTITY; } else { HmdMatrix34_t mat = vrsystemFunctions.GetEyeToHeadTransform.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left); hmdPoseLeftEye = new Matrix4f(); return VRUtil.convertSteamVRMatrix3ToMatrix4f(mat, hmdPoseLeftEye); } } @Override public HmdType getType() { if( vrsystemFunctions != null ) { Pointer str1 = new Memory(128); Pointer str2 = new Memory(128); String completeName = ""; vrsystemFunctions.GetStringTrackedDeviceProperty.apply(JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd, JOpenVRLibrary.ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ManufacturerName_String, str1, 128, hmdErrorStore); if( hmdErrorStore.get(0) == 0 ) completeName += str1.getString(0); vrsystemFunctions.GetStringTrackedDeviceProperty.apply(JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd, JOpenVRLibrary.ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ModelNumber_String, str2, 128, hmdErrorStore); if( hmdErrorStore.get(0) == 0 ) completeName += " " + str2.getString(0); if( completeName.length() > 0 ) { completeName = completeName.toLowerCase(Locale.ENGLISH).trim(); if( completeName.contains("htc") || completeName.contains("vive") ) { return HmdType.HTC_VIVE; } else if( completeName.contains("osvr") ) { return HmdType.OSVR; } else if( completeName.contains("oculus") || completeName.contains("rift") || completeName.contains("dk1") || completeName.contains("dk2") || completeName.contains("cv1") ) { return HmdType.OCULUS_RIFT; } else if( completeName.contains("fove") ) { return HmdType.FOVE; } else if( completeName.contains("game") && completeName.contains("face") ) { return HmdType.GAMEFACE; } else if( completeName.contains("morpheus") ) { return HmdType.MORPHEUS; } else if( completeName.contains("gear") ) { return HmdType.GEARVR; } else if( completeName.contains("star") ) { return HmdType.STARVR; } else if( completeName.contains("null") ) { return HmdType.NULL; } } } else return HmdType.NONE; return HmdType.OTHER; } @Override public Matrix4f getHMDMatrixPoseRightEye(){ if( hmdPoseRightEye != null ) { return hmdPoseRightEye; } else if(vrsystemFunctions == null) { return Matrix4f.IDENTITY; } else { HmdMatrix34_t mat = vrsystemFunctions.GetEyeToHeadTransform.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right); hmdPoseRightEye = new Matrix4f(); return VRUtil.convertSteamVRMatrix3ToMatrix4f(mat, hmdPoseRightEye); } } @Override public VRApplication getApplication() { return application; } }