diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index bbb795fdf..10b96aa7f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Spatial.java +++ b/jme3-core/src/main/java/com/jme3/scene/Spatial.java @@ -170,6 +170,60 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab */ protected transient int refreshFlags = 0; + public void refreshFlagOr(int flag){ + refreshFlags |= flag; + //logger.warning("Changing refresh flags for spatial " + getName()+", flags: "+getRefreshFlagsDescription()); + } + + public void refreshFlagAnd(int flag){ + refreshFlags &= flag; + //logger.warning("Changing refresh flags for spatial " + getName()+", flags: "+getRefreshFlagsDescription()); + } + + public int refreshFlagGetAnd(int flag){ + return (refreshFlags & flag); + } + + public int getRefreshFlags(){ + return refreshFlags; + } + + public String getRefreshFlagsDescription(){ + String str = ""; + + if (refreshFlags == 0){ + str += "OK"; + } else { + + // need light resort + combine transforms + if ((refreshFlags & RF_TRANSFORM) == RF_TRANSFORM){ + str += "RF_TRANSFORM "; + } + + // need light resort + combine transforms + if ((refreshFlags & RF_BOUND) == RF_BOUND){ + str += "RF_BOUND "; + } + + // changes in light lists + if ((refreshFlags & RF_LIGHTLIST) == RF_LIGHTLIST){ + str += "RF_LIGHTLIST "; + } + + // some child need geometry update + if ((refreshFlags & RF_CHILD_LIGHTLIST) == RF_CHILD_LIGHTLIST){ + str += "RF_CHILD_LIGHTLIST "; + } + + // some child need geometry update + if ((refreshFlags & RF_MATPARAM_OVERRIDE) == RF_MATPARAM_OVERRIDE){ + str += "RF_MATPARAM_OVERRIDE "; + } + } + + return str; + } + /** * Set to true if a subclass requires updateLogicalState() even * if it doesn't have any controls. Defaults to true thus implementing @@ -209,7 +263,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab localOverrides = new SafeArrayList<>(MatParamOverride.class); worldOverrides = new SafeArrayList<>(MatParamOverride.class); - refreshFlags |= RF_BOUND; + refreshFlagOr(RF_BOUND); } public void setKey(AssetKey key) { @@ -272,35 +326,35 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * a refresh is required. */ protected void setTransformRefresh() { - refreshFlags |= RF_TRANSFORM; + refreshFlagOr(RF_TRANSFORM); setBoundRefresh(); } protected void setLightListRefresh() { - refreshFlags |= RF_LIGHTLIST; + refreshFlagOr(RF_LIGHTLIST); // Make sure next updateGeometricState() visits this branch // to update lights. Spatial p = parent; while (p != null) { - if ((p.refreshFlags & RF_CHILD_LIGHTLIST) != 0) { + if ((p.refreshFlagGetAnd(RF_CHILD_LIGHTLIST)) != 0) { // The parent already has this flag, // so must all ancestors. return; } - p.refreshFlags |= RF_CHILD_LIGHTLIST; + p.refreshFlagOr(RF_CHILD_LIGHTLIST); p = p.parent; } } protected void setMatParamOverrideRefresh() { - refreshFlags |= RF_MATPARAM_OVERRIDE; + refreshFlagOr(RF_MATPARAM_OVERRIDE); Spatial p = parent; while (p != null) { - if ((p.refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { + if ((p.refreshFlagGetAnd(RF_MATPARAM_OVERRIDE)) != 0) { return; } - p.refreshFlags |= RF_MATPARAM_OVERRIDE; + p.refreshFlagOr(RF_MATPARAM_OVERRIDE); p = p.parent; } } @@ -310,15 +364,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * a refresh is required. */ protected void setBoundRefresh() { - refreshFlags |= RF_BOUND; + refreshFlagOr(RF_BOUND); Spatial p = parent; while (p != null) { - if ((p.refreshFlags & RF_BOUND) != 0) { + if ((p.refreshFlagGetAnd(RF_BOUND)) != 0) { return; } - p.refreshFlags |= RF_BOUND; + p.refreshFlagOr(RF_BOUND); p = p.parent; } } @@ -353,11 +407,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * (should be rendered), false if outside. */ public boolean checkCulling(Camera cam) { - if (refreshFlags != 0) { + if (getRefreshFlags() != 0) { + /* throw new IllegalStateException("Scene graph is not properly updated for rendering.\n" + "State was changed after rootNode.updateGeometricState() call. \n" + "Make sure you do not modify the scene from another thread!\n" - + "Problem spatial name: " + getName()); + + "Problem spatial name: " + getName()+", flags: "+getRefreshFlagsDescription()); + */ + logger.warning("Invalid refresh flags for spatial " + getName()+", flags: "+getRefreshFlagsDescription()); } CullHint cm = getCullHint(); @@ -571,28 +628,28 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab // for a node, the world bound is a combination of all it's children // bounds // -> handled by subclass - refreshFlags &= ~RF_BOUND; + refreshFlagAnd(~RF_BOUND); } protected void updateWorldLightList() { if (parent == null) { worldLights.update(localLights, null); - refreshFlags &= ~RF_LIGHTLIST; + refreshFlagAnd(~RF_LIGHTLIST); } else { - assert (parent.refreshFlags & RF_LIGHTLIST) == 0; + assert (parent.refreshFlagGetAnd(RF_LIGHTLIST)) == 0; worldLights.update(localLights, parent.worldLights); - refreshFlags &= ~RF_LIGHTLIST; + refreshFlagAnd(~RF_LIGHTLIST); } } protected void updateMatParamOverrides() { - refreshFlags &= ~RF_MATPARAM_OVERRIDE; + refreshFlagAnd(~RF_MATPARAM_OVERRIDE); worldOverrides.clear(); if (parent == null) { worldOverrides.addAll(localOverrides); } else { - assert (parent.refreshFlags & RF_MATPARAM_OVERRIDE) == 0; + assert (parent.refreshFlagGetAnd(RF_MATPARAM_OVERRIDE)) == 0; worldOverrides.addAll(parent.worldOverrides); worldOverrides.addAll(localOverrides); } @@ -643,13 +700,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab protected void updateWorldTransforms() { if (parent == null) { worldTransform.set(localTransform); - refreshFlags &= ~RF_TRANSFORM; + refreshFlagAnd(~RF_TRANSFORM); } else { // check if transform for parent is updated - assert ((parent.refreshFlags & RF_TRANSFORM) == 0); + assert ((parent.refreshFlagGetAnd(RF_TRANSFORM)) == 0); worldTransform.set(localTransform); worldTransform.combineWithParent(parent.worldTransform); - refreshFlags &= ~RF_TRANSFORM; + refreshFlagAnd(~RF_TRANSFORM); } } @@ -658,13 +715,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * efficient manner possible. */ void checkDoTransformUpdate() { - if ((refreshFlags & RF_TRANSFORM) == 0) { + if ((refreshFlagGetAnd(RF_TRANSFORM)) == 0) { return; } if (parent == null) { worldTransform.set(localTransform); - refreshFlags &= ~RF_TRANSFORM; + refreshFlagAnd(~RF_TRANSFORM); } else { TempVars vars = TempVars.get(); @@ -675,14 +732,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab Spatial hisParent = rootNode.parent; if (hisParent == null) { rootNode.worldTransform.set(rootNode.localTransform); - rootNode.refreshFlags &= ~RF_TRANSFORM; + rootNode.refreshFlagAnd(~RF_TRANSFORM); i--; break; } stack[i] = rootNode; - if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) { + if ((hisParent.refreshFlagGetAnd(RF_TRANSFORM)) == 0) { break; } @@ -707,7 +764,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * manner possible. */ void checkDoBoundUpdate() { - if ((refreshFlags & RF_BOUND) == 0) { + if ((refreshFlagGetAnd(RF_BOUND)) == 0) { return; } @@ -897,20 +954,20 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab // NOTE: Update world transforms first because // bound transform depends on them. - if ((refreshFlags & RF_LIGHTLIST) != 0) { + if ((refreshFlagGetAnd(RF_LIGHTLIST)) != 0) { updateWorldLightList(); } - if ((refreshFlags & RF_TRANSFORM) != 0) { + if ((refreshFlagGetAnd(RF_TRANSFORM)) != 0) { updateWorldTransforms(); } - if ((refreshFlags & RF_BOUND) != 0) { + if ((refreshFlagGetAnd(RF_BOUND)) != 0) { updateWorldBound(); } - if ((refreshFlags & RF_MATPARAM_OVERRIDE) != 0) { + if ((refreshFlagGetAnd(RF_MATPARAM_OVERRIDE)) != 0) { updateMatParamOverrides(); } - assert refreshFlags == 0; + assert getRefreshFlags() == 0; } /** diff --git a/jme3-vr/src/main/java/com/jme3/app/VRAppState.java b/jme3-vr/src/main/java/com/jme3/app/VRAppState.java new file mode 100644 index 000000000..1c91977f3 --- /dev/null +++ b/jme3-vr/src/main/java/com/jme3/app/VRAppState.java @@ -0,0 +1,843 @@ +package com.jme3.app; + + +import com.jme3.app.Application; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.input.vr.OSVR; +import com.jme3.input.vr.OpenVR; +import com.jme3.input.vr.VRAPI; +import com.jme3.input.vr.VRInputAPI; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.PreNormalCaching; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import com.jme3.system.jopenvr.JOpenVRLibrary; + +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.Iterator; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jmevr.util.VRGuiManager; +import jmevr.util.VRMouseManager; +import jmevr.util.VRViewManager; +import jmevr.util.VRGuiManager.POSITIONING_MODE; + +/** + * A JMonkey app state dedicated to Virtual Reality. + * An application that want to use VR devices (HTC vive, ...) has to use this app state.
+ * As this app state and the main {@link Application application} have to share {@link AppSettings application settings}, + * the common way to use this app state is:
+ * + * Attaching an instance of this app state to an already started application may cause crashes. + * @author Julien Seinturier - JOrigin project - http:/www.jorigin.org + */ +public class VRAppState extends AbstractAppState { + + private static final Logger logger = Logger.getLogger(VRAppState.class.getName()); + + /** + * The underlying system VR API. By default set to {@link VRConstants#SETTING_VRAPI_OPENVR_VALUE}. + */ + public int vrBinding = VRConstants.SETTING_VRAPI_OPENVR_VALUE; + + /** + * Is the application has not to start within VR mode (default is false). + */ + public boolean DISABLE_VR = false; + + private VRAPI VRhardware = null; + private VRGuiManager guiManager = null; + private VRMouseManager mouseManager = null; + private VRViewManager viewmanager = null; + + private String OS; + + private Camera dummyCam; + + private Spatial observer = null; + + private boolean VRSupportedOS; + private boolean forceVR = false;; + private boolean disableSwapBuffers = true; + private boolean disableVR = false; + private boolean seated; + private boolean nogui; + private boolean instanceVR = false; + + private float defaultFOV = 108f; + private float defaultAspect = 1f; + + + + private float fFar = 1000f; + private float fNear = 0.1f; + private int xWin = 1920; + private int yWin = 1080; + + private float resMult = 1f; + + private boolean useCompositor = true; + private boolean compositorOS; + + /* + where is the headset pointing, after all rotations are combined? + depends on observer rotation, if any + */ + private Quaternion tempq = new Quaternion(); + + private Application application = null; + private AppStateManager stateManager = null; + private AppSettings settings = null; + + + /** + * Create a new default VR app state. + */ + public VRAppState() { + super(); + + dummyCam = new Camera(); + + // Create the GUI manager. + guiManager = new VRGuiManager(); + + // Create a new view manager. + viewmanager = new VRViewManager(); + + // Create a new mouse manager. + mouseManager = new VRMouseManager(); + + } + + /** + * Create a new VR app state with given settings. + * @param settings the settings to use. + */ + public VRAppState(AppSettings settings){ + this(); + this.settings = settings; + processSettings(settings); + } + + + /** + * Simple update of the app state, this method should contains any spatial updates. + * This method is called by the {@link #update(float) update()} method and should not be called manually. + * @param tpf the application time. + */ + public void simpleUpdate(float tpf) { + return; + } + + /** + * Rendering callback of the app state. This method is called by the {@link #update(float) update()} method and should not be called manually. + * @param renderManager the {@link RenderManager render manager}. + */ + public void simpleRender(RenderManager renderManager) { + PreNormalCaching.resetCache(isInVR()); + } + + /** + * Set the frustrum values for the application. + * @param near the frustrum near value. + * @param far the frustrum far value. + */ + public void setFrustrumNearFar(float near, float far) { + fNear = near; + fFar = far; + } + + /** + * Set the mirror window size in pixel. + * @param width the width of the mirror window in pixel. + * @param height the height of the mirror window in pixel. + */ + public void setMirrorWindowSize(int width, int height) { + xWin = width; + yWin = height; + } + + /** + * Set the resolution multiplier. + * @param val the resolution multiplier. + */ + public void setResolutionMultiplier(float val) { + resMult = val; + if( viewmanager != null ){ + viewmanager.setResolutionMultiplier(resMult); + } + } + + /** + * Is the VR compositor is active. + * @return true if the VR compositor is active and false otherwise. + */ + public boolean compositorAllowed() { + return useCompositor && compositorOS; + } + + /** + * Get if the system currently support VR. + * @return true if the system currently support VR and false otherwise. + */ + public boolean isVRSupported() { + return VRSupportedOS; + } + + /** + * Get the {@link Camera camera} attached to this application state. + * If the VR mode is {@link #isInVR() active}, this method return a dummy camera, otherwise, + * this method return the camera of the attached application. + * @return the camera attached to this application state. + */ + public Camera getCamera() { + if( isInVR() && viewmanager != null && viewmanager.getLeftCamera() != null ) { + return dummyCam; + } + + return application.getCamera(); + } + + /** + * Can be used to change seated experience during runtime. + * @param isSeated true if designed for sitting, false for standing/roomscale + * @see #isSeatedExperience() + */ + public void setSeatedExperience(boolean isSeated) { + seated = isSeated; + if( VRhardware instanceof OpenVR ) { + if( VRhardware.getCompositor() == null ) return; + if( seated ) { + ((OpenVR)VRhardware).getCompositor().SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated); + } else { + ((OpenVR)VRhardware).getCompositor().SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding); + } + } + } + + /** + * Check if the application is configured as a seated experience. + * @return true if the application is configured as a seated experience and false otherwise. + * @see #setSeatedExperience(boolean) + */ + public boolean isSeatedExperience() { + return seated; + } + + /** + * Reset headset pose if seating experience. + */ + public void resetSeatedPose(){ + if( VRSupportedOS == false || isSeatedExperience() == false ) return; + VRhardware.reset(); + } + + /** + * Check if the rendering is instanced (see Geometry instancing). + * @return true if the rendering is instanced and false otherwise. + */ + public boolean isInstanceVRRendering() { + return instanceVR && isInVR(); + } + + /** + * Check if the VR mode is enabled. + * @return true if the VR mode is enabled and false otherwise. + */ + public boolean isInVR() { + return DISABLE_VR == false && (forceVR || VRSupportedOS && VRhardware != null && VRhardware.isInitialized()); + } + + /** + * Get the default Field Of View (FOV) value. + * @return the default Field Of View (FOV) value. + * @see #setDefaultFOV(float) + */ + public float getDefaultFOV() { + return defaultFOV; + } + + /** + * Set the default Field Of View (FOV) value. + * @param defaultFOV the default Field Of View (FOV) value. + * @see #getDefaultFOV() + */ + public void setDefaultFOV(float defaultFOV) { + this.defaultFOV = defaultFOV; + } + + /** + * Get the default aspect ratio. + * @return the default aspect ratio. + * @see #setDefaultAspect(float) + */ + public float getDefaultAspect() { + return defaultAspect; + } + + /** + * Set the default aspect ratio. + * @param defaultAspect the default aspect ratio. + * @see #getDefaultAspect() + */ + public void setDefaultAspect(float defaultAspect) { + this.defaultAspect = defaultAspect; + } + + /** + * Move filters from the main scene into the eye's. + * This removes filters from the main scene. + */ + public void moveScreenProcessingToVR() { + if( isInVR() ) { + viewmanager.moveScreenProcessingToEyes(); + } + } + + /** + * Check if the application has a GUI overlay attached. + * @return true if the application has a GUI overlay attached and false otherwise. + */ + public boolean hasTraditionalGUIOverlay() { + return !nogui; + } + + /** + * Get the scene observer. If no observer has been set, this method return the application {@link #getCamera() camera}. + * @return the scene observer. + * @see #setObserver(Spatial) + */ + public Object getObserver() { + if( observer == null ) { + return getCamera(); + } + return observer; + } + + /** + * Set the scene observer. The VR headset will be linked to it. If no observer is set, the VR headset is linked to the the application {@link #getCamera() camera}. + * @param observer the scene observer. + */ + public void setObserver(Spatial observer) { + this.observer = observer; + } + + /** + * Get the observer final rotation within the scene. + * @return the observer final rotation within the scene. + * @see #getFinalObserverPosition() + */ + public Quaternion getFinalObserverRotation() { + if( viewmanager == null ) { + if( observer == null ) { + return getCamera().getRotation(); + } else return observer.getWorldRotation(); + } + if( observer == null ) { + tempq.set(dummyCam.getRotation()); + } else { + tempq.set(observer.getWorldRotation()); + } + return tempq.multLocal(VRhardware.getOrientation()); + } + + /** + * Get the observer final position within the scene. + * @return the observer position. + * @see #getFinalObserverRotation() + */ + public Vector3f getFinalObserverPosition() { + if( viewmanager == null ) { + if( observer == null ) { + return getCamera().getLocation(); + } else return observer.getWorldTranslation(); + } + Vector3f pos = VRhardware.getPosition(); + if( observer == null ) { + dummyCam.getRotation().mult(pos, pos); + return pos.addLocal(dummyCam.getLocation()); + } else { + observer.getWorldRotation().mult(pos, pos); + return pos.addLocal(observer.getWorldTranslation()); + } + } + + /** + * Set the VR headset height from the ground. + * @param amount the VR headset height from the ground. + * @see #getVRHeightAdjustment() + */ + public void setVRHeightAdjustment(float amount) { + if( viewmanager != null ) viewmanager.setHeightAdjustment(amount); + } + + /** + * Get the VR headset height from the ground. + * @return the VR headset height from the ground. + * @see #setVRHeightAdjustment(float) + */ + public float getVRHeightAdjustment() { + if( viewmanager != null ){ + return viewmanager.getHeightAdjustment(); + } + return 0f; + } + + /** + * Get the VR headset left viewport. + * @return the VR headset left viewport. + * @see #getRightViewPort() + */ + public ViewPort getLeftViewPort() { + if( viewmanager == null ) return application.getViewPort(); + return viewmanager.getLeftViewport(); + } + + /** + * Get the VR headset right viewport. + * @return the VR headset right viewport. + * @see #getLeftViewPort() + */ + public ViewPort getRightViewPort() { + if( viewmanager == null ) return application.getViewPort(); + return viewmanager.getRightViewport(); + } + + /** + * Set the background color for both left and right view ports. + * @param clr the background color. + */ + public void setBackgroundColors(ColorRGBA clr) { + if( viewmanager == null ) { + application.getViewPort().setBackgroundColor(clr); + } else if( viewmanager.getLeftViewport() != null ) { + viewmanager.getLeftViewport().setBackgroundColor(clr); + if( viewmanager.getRightViewport() != null ) viewmanager.getRightViewport().setBackgroundColor(clr); + } + } + + /** + * Get the {@link Application} to which this app state is attached. + * @return the {@link Application} to which this app state is attached. + * @see #getStateManager() + */ + public Application getApplication(){ + return application; + } + + /** + * Get the {@link AppStateManager state manager} to which this app state is attached. + * @return the {@link AppStateManager state manager} to which this app state is attached. + * @see #getApplication() + */ + public AppStateManager getStateManager(){ + return stateManager; + } + + /** + * Get the VR underlying hardware. + * @return the VR underlying hardware. + */ + public VRAPI getVRHardware() { + return VRhardware; + } + + /** + * Get the VR dedicated input. + * @return the VR dedicated input. + */ + public VRInputAPI getVRinput() { + if( VRhardware == null ){ + return null; + } + + return VRhardware.getVRinput(); + } + + /** + * Get the VR view manager. + * @return the VR view manager. + */ + public VRViewManager getVRViewManager() { + return viewmanager; + } + + /** + * Get the GUI manager attached to this application. + * @return the GUI manager attached to this application. + */ + public VRGuiManager getVRGUIManager(){ + return guiManager; + } + + /** + * Get the VR mouse manager attached to this application. + * @return the VR mouse manager attached to this application. + */ + public VRMouseManager getVRMouseManager(){ + return mouseManager; + } + + /** + * Get the {@link AppSettings settings} attached to this app state. + * @return the {@link AppSettings settings} attached to this app state. + * @see #setSettings(AppSettings) + */ + public AppSettings getSettings(){ + return settings; + } + + /** + * Set the {@link AppSettings settings} attached to this app state. + * @param settings the {@link AppSettings settings} attached to this app state. + * @see #getSettings() + */ + public void setSettings(AppSettings settings){ + this.settings = settings; + processSettings(settings); + } + + @Override + public void update(float tpf) { + + // update VR pose & cameras + if( viewmanager != null ) { + viewmanager.update(tpf); + } else if( observer != null ) { + getCamera().setFrame(observer.getWorldTranslation(), observer.getWorldRotation()); + } + + //FIXME: check if this code is necessary. + // Updates scene and gui states. + Iterator spatialIter = application.getViewPort().getScenes().iterator(); + Spatial spatial = null; + while(spatialIter.hasNext()){ + spatial = spatialIter.next(); + spatial.updateLogicalState(tpf); + spatial.updateGeometricState(); + } + + if( isInVR() == false || guiManager.getPositioningMode() == POSITIONING_MODE.MANUAL ) { + // only update geometric state here if GUI is in manual mode, or not in VR + // it will get updated automatically in the viewmanager update otherwise + spatialIter = application.getGuiViewPort().getScenes().iterator(); + spatial = null; + while(spatialIter.hasNext()){ + spatial = spatialIter.next(); + spatial.updateGeometricState(); + } + } + + + // use the analog control on the first tracked controller to push around the mouse + getVRMouseManager().updateAnalogAsMouse(0, null, null, null, tpf); + } + + @Override + public void postRender() { + super.postRender(); + // update compositor? + if( viewmanager != null ) { + viewmanager.sendTextures(); + } + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + super.initialize(stateManager, app); + + this.application = app; + this.stateManager = stateManager; + + // disable annoying warnings about GUI stuff being updated, which is normal behavior + // for late GUI placement for VR purposes + Logger.getLogger("com.jme3").setLevel(Level.SEVERE); + + // VR module attch + guiManager.attach(this, app); + viewmanager.attach(this, app); + mouseManager.attach(this, app); + + app.getCamera().setFrustumFar(fFar); + app.getCamera().setFrustumNear(fNear); + dummyCam = app.getCamera().clone(); + + if( isInVR() ) { + + logger.config("VR mode enabled."); + + if( VRhardware != null ) { + VRhardware.initVRCompositor(compositorAllowed()); + } else { + logger.warning("No VR system found."); + } + + + viewmanager.setResolutionMultiplier(resMult); + //inputManager.addMapping(RESET_HMD, new KeyTrigger(KeyInput.KEY_F9)); + //setLostFocusBehavior(LostFocusBehavior.Disabled); + } else { + logger.config("VR mode disabled."); + //viewPort.attachScene(rootNode); + //guiViewPort.attachScene(guiNode); + } + + if( viewmanager != null ) { + viewmanager.initialize(); + } + } + + @Override + public void stateAttached(AppStateManager stateManager) { + super.stateAttached(stateManager); //To change body of generated methods, choose Tools | Templates. + + if (settings == null) { + settings = new AppSettings(true); + logger.config("Using default settings."); + } else { + logger.config("Using given settings."); + } + + // we are going to use OpenVR now, not the Oculus Rift + // OpenVR does support the Rift + OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); + VRSupportedOS = !OS.contains("nux") && System.getProperty("sun.arch.data.model").equalsIgnoreCase("64"); //for the moment, linux/unix causes crashes, 64-bit only + compositorOS = OS.contains("indows"); + + if( VRSupportedOS && disableVR == false ) { + if( vrBinding == VRConstants.SETTING_VRAPI_OSVR_VALUE ) { + VRhardware = new OSVR(this); + logger.config("Creating OSVR wrapper [SUCCESS]"); + } else if( vrBinding == VRConstants.SETTING_VRAPI_OPENVR_VALUE ) { + VRhardware = new OpenVR(this); + logger.config("Creating OpenVR wrapper [SUCCESS]"); + } else { + logger.config("Cannot create VR binding: "+vrBinding+" [FAILED]"); + } + + if( VRhardware.initialize() ) { + logger.config("VR native wrapper initialized [SUCCESS]"); + } else { + logger.warning("VR native wrapper initialized [FAILED]"); + } + } + + GraphicsDevice defDev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + + if( isInVR() && !compositorAllowed() ) { + // "easy extended" mode + // setup experimental JFrame on external device + // first, find the VR device + GraphicsDevice VRdev = null; + GraphicsDevice[] devs = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); + // pick the display that isn't the default one + for(GraphicsDevice gd : devs) { + if( gd != defDev ) { + VRdev = gd; + break; + } + } + + // did we get the VR device? + if( VRdev != null ) { + // set properties for VR acceleration + try { + java.awt.DisplayMode useDM = null; + int max = 0; + for(java.awt.DisplayMode dm : VRdev.getDisplayModes()) { + int check = dm.getHeight() + dm.getWidth() + dm.getRefreshRate() + dm.getBitDepth(); + if( check > max ) { + max = check; + useDM = dm; + } + } + + // create a window for the VR device + settings.setWidth(useDM.getWidth()); + settings.setHeight(useDM.getHeight()); + settings.setBitsPerPixel(useDM.getBitDepth()); + settings.setFrequency(useDM.getRefreshRate()); + settings.setSwapBuffers(true); + settings.setVSync(true); // allow vsync on this display + stateManager.getApplication().setSettings(settings); + logger.config("Updated underlying application settings."); + + //VRdev.setFullScreenWindow(VRwindow); + // make sure we are in the right display mode + if( VRdev.getDisplayMode().equals(useDM) == false ) { + VRdev.setDisplayMode(useDM); + } + + return; + } catch(Exception e) { + logger.log(Level.SEVERE, e.getMessage(), e); + } + } else { + logger.config("Cannot access to external screen."); + } + } else { + if (!isInVR()){ + logger.config("Cannot switch to VR mode (VR disabled by user)."); + } else if (!compositorAllowed()){ + logger.warning("Cannot switch to VR mode (VR not supported)."); + } + } + + if( !isInVR() ) { + + //FIXME: Handling GLFW workaround on MacOS + boolean macOs = false; + if (macOs) { + // GLFW workaround on macs + settings.setFrequency(defDev.getDisplayMode().getRefreshRate()); + settings.setDepthBits(24); + settings.setVSync(true); + // try and read resolution from file in local dir + File resfile = new File("resolution.txt"); + if( resfile.exists() ) { + try { + BufferedReader br = new BufferedReader(new FileReader(resfile)); + settings.setWidth(Integer.parseInt(br.readLine())); + settings.setHeight(Integer.parseInt(br.readLine())); + try { + settings.setFullscreen(br.readLine().toLowerCase(Locale.ENGLISH).contains("full")); + } catch(Exception e) { + settings.setFullscreen(false); + } + br.close(); + } catch(Exception e) { + settings.setWidth(1280); + settings.setHeight(720); + } + } else { + settings.setWidth(1280); + settings.setHeight(720); + settings.setFullscreen(false); + } + settings.setResizable(false); + } + settings.setSwapBuffers(true); + } else { + // use basic mirroring window, skip settings window + settings.setSamples(1); + settings.setWidth(xWin); + settings.setHeight(yWin); + settings.setBitsPerPixel(32); + settings.setFrameRate(0); + settings.setFrequency(VRhardware.getDisplayFrequency()); + settings.setFullscreen(false); + settings.setVSync(false); // stop vsyncing on primary monitor! + settings.setSwapBuffers(disableSwapBuffers); + } + + // Updating application settings + stateManager.getApplication().setSettings(settings); + logger.config("Updated underlying application settings."); + + } + + @Override + public void cleanup() { + if( VRhardware != null ) { + VRhardware.destroy(); + VRhardware = null; + } + disableVR = true; + + this.application = null; + this.stateManager = null; + } + + @Override + public void stateDetached(AppStateManager stateManager) { + super.stateDetached(stateManager); + } + + /** + * Process the attached settings and apply changes to this app state. + * @param settings the app settings to process. + */ + protected void processSettings(AppSettings settings){ + if (settings != null){ + if (settings.get(VRConstants.SETTING_USE_COMPOSITOR) != null){ + useCompositor = settings.getBoolean(VRConstants.SETTING_USE_COMPOSITOR); + if( useCompositor == false ){ + disableSwapBuffers = false; + } + } + + if (settings.get(VRConstants.SETTING_VR_FORCE) != null){ + forceVR = settings.getBoolean(VRConstants.SETTING_VR_FORCE); + } + + if (settings.get(VRConstants.SETTING_FLIP_EYES) != null){ + if( VRhardware != null ){ + VRhardware._setFlipEyes(settings.getBoolean(VRConstants.SETTING_FLIP_EYES)); + } + } + + if (settings.get(VRConstants.SETTING_GUI_OVERDRAW) != null){ + guiManager._enableGuiOverdraw(settings.getBoolean(VRConstants.SETTING_GUI_OVERDRAW)); + } + + if (settings.get(VRConstants.SETTING_GUI_CURVED_SURFACE) != null){ + guiManager._enableCurvedSuface(settings.getBoolean(VRConstants.SETTING_GUI_CURVED_SURFACE)); + } + + if (settings.get(VRConstants.SETTING_ENABLE_MIRROR_WINDOW) != null){ + if( useCompositor == false ) { + disableSwapBuffers = false; + } else { + disableSwapBuffers = !settings.getBoolean(VRConstants.SETTING_ENABLE_MIRROR_WINDOW); + } + } + + if (settings.get(VRConstants.SETTING_DISABLE_VR) != null){ + DISABLE_VR = settings.getBoolean(VRConstants.SETTING_DISABLE_VR); + } + + if (settings.get(VRConstants.SETTING_SEATED_EXPERIENCE) != null){ + seated = settings.getBoolean(VRConstants.SETTING_SEATED_EXPERIENCE); + } + + if (settings.get(VRConstants.SETTING_NO_GUI) != null){ + nogui = settings.getBoolean(VRConstants.SETTING_NO_GUI); + } + + if (settings.get(VRConstants.SETTING_INSTANCE_RENDERING) != null){ + instanceVR = settings.getBoolean(VRConstants.SETTING_INSTANCE_RENDERING); + } + + if (settings.get(VRConstants.SETTING_DEFAULT_FOV) != null){ + defaultFOV = settings.getFloat(VRConstants.SETTING_DEFAULT_FOV); + } + + if (settings.get(VRConstants.SETTING_DEFAULT_ASPECT_RATIO) != null){ + defaultAspect = settings.getFloat(VRConstants.SETTING_DEFAULT_ASPECT_RATIO); + } + + if (settings.get(VRConstants.SETTING_VRAPI) != null){ + vrBinding = settings.getInteger(VRConstants.SETTING_VRAPI); + } + + } + } +} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/app/VRApplication.java b/jme3-vr/src/main/java/com/jme3/app/VRApplication.java index 0113dac92..f4fffbb89 100644 --- a/jme3-vr/src/main/java/com/jme3/app/VRApplication.java +++ b/jme3-vr/src/main/java/com/jme3/app/VRApplication.java @@ -255,13 +255,13 @@ public abstract class VRApplication implements Application, SystemListener { initStateManager(); // Create the GUI manager. - guiManager = new VRGuiManager(this); + guiManager = new VRGuiManager(); // Create a new view manager. - viewmanager = new VRViewManager(this); + viewmanager = new VRViewManager(); // Create a new mouse manager. - mouseManager = new VRMouseManager(this); + mouseManager = new VRMouseManager(); // we are going to use OpenVR now, not the Oculus Rift // OpenVR does support the Rift @@ -275,10 +275,12 @@ public abstract class VRApplication implements Application, SystemListener { logger.warning("VR disabled via code."); } else if( VRSupportedOS && DISABLE_VR == false ) { if( CONSTRUCT_WITH_OSVR ) { - VRhardware = new OSVR(this); + //FIXME: WARNING !! + VRhardware = new OSVR(null); logger.config("Creating OSVR wrapper [SUCCESS]"); } else { - VRhardware = new OpenVR(this); + //FIXME: WARNING !! + VRhardware = new OpenVR(null); logger.config("Creating OpenVR wrapper [SUCCESS]"); } if( VRhardware.initialize() ) { @@ -820,6 +822,16 @@ public abstract class VRApplication implements Application, SystemListener { context.create(waitFor); } + /** + * Move filters from the main scene into the eye's. + * This removes filters from the main scene. + */ + public void moveScreenProcessingToVR() { + if( isInVR() ) { + viewmanager.moveScreenProcessingToEyes(); + } + } + /** * Set VR application {@link PreconfigParameter specific parameter}. * If making changes to default values, this must be called before the VRApplication starts @@ -924,15 +936,6 @@ public abstract class VRApplication implements Application, SystemListener { return DISABLE_VR == false && (forceVR || VRSupportedOS && VRhardware != null && VRhardware.isInitialized()); } - /** - * Move filters from the main scene into the eye's. - * This removes filters from the main scene. - */ - public void moveScreenProcessingToVR() { - if( isInVR() ) { - viewmanager.moveScreenProcessingToEyes(); - } - } /** * Get the GUI node from the application. @@ -1354,7 +1357,8 @@ public abstract class VRApplication implements Application, SystemListener { logger.warning("No VR system found."); } - viewmanager = new VRViewManager(this); + //FIXME: WARNING !! + viewmanager = new VRViewManager(); viewmanager.setResolutionMultiplier(resMult); inputManager.addMapping(RESET_HMD, new KeyTrigger(KeyInput.KEY_F9)); setLostFocusBehavior(LostFocusBehavior.Disabled); diff --git a/jme3-vr/src/main/java/com/jme3/app/VRConstants.java b/jme3-vr/src/main/java/com/jme3/app/VRConstants.java new file mode 100644 index 000000000..6e23ec2bf --- /dev/null +++ b/jme3-vr/src/main/java/com/jme3/app/VRConstants.java @@ -0,0 +1,147 @@ +package com.jme3.app; + +import java.util.HashMap; + +import com.jme3.system.AppSettings; + +/** + * Some constants dedicated to the VR module. + * @author Julien Seinturier - JOrigin project - http:/www.jorigin.org + * @since 3.1.0 + */ +public class VRConstants { + + /** + * An AppSettings parameter that set if the VR compositor has to be used. + *

+ * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_USE_COMPOSITOR, value) + */ + public static final String SETTING_USE_COMPOSITOR = "VRUseCompositor"; + + /** + * An AppSettings parameter that set if the rendering has to use two eyes, + * regardless of VR API detection (turning this setting on without a VR system should lead to errors). + *

+ * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_VR_FORCE, value) + + */ + public static final String SETTING_VR_FORCE = "VRForce"; + + /** + * An AppSettings parameter that set to invert the eyes of the HMD. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_FLIP_EYES, value) + */ + public static final String SETTING_FLIP_EYES = "VRFlipEyes"; + + /** + * An AppSettings parameter that set if the GUI has to be displayed even if it is behind objects. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_GUI_OVERDRAW, value) + * + */ + public static final String SETTING_GUI_OVERDRAW = "VRGUIOverdraw"; + + /** + * An AppSettings parameter that set if the GUI surface has to be curved. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_GUI_CURVED_SURFACE, value) + */ + public static final String SETTING_GUI_CURVED_SURFACE = "VRGUICurvedSurface"; + + /** + * An AppSettings parameter that set if a mirror rendering has to be displayed on the screen. + * Runs faster when set to false. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_ENABLE_MIRROR_WINDOW, value) + */ + public static final String SETTING_ENABLE_MIRROR_WINDOW = "VREnableMirrorWindow"; + + /** + * An AppSettings parameter that set if the VR rendering has to be disabled, + * regardless VR API and devices are presents. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DISABLE_VR, value) + */ + public static final String SETTING_DISABLE_VR = "VRDisable"; + + + /** + * An AppSettings parameter that set if the VR user is seated. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_SEATED_EXPERIENCE, value) + */ + public static final String SETTING_SEATED_EXPERIENCE = "VRSeatedExperience"; + + /** + * An AppSettings parameter that set if the GUI has to be ignored. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_NO_GUI, value) + */ + public static final String SETTING_NO_GUI = "VRNoGUI"; + + /** + * An AppSettings parameter that set if instance rendering has to be used. + * This setting requires some vertex shader changes (see Common/MatDefs/VR/Unshaded.j3md). + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_INSTANCE_RENDERING, value) + */ + public static final String SETTING_INSTANCE_RENDERING = "VRInstanceRendering"; + + /** + * An AppSettings parameter that set if Multi Sample Anti Aliasing has to be enabled. + * Type: boolean
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DISABLE_MSAA, value) + */ + public static final String SETTING_DISABLE_MSAA = "VRDisableMSAA"; + + /** + * An AppSettings parameter that set the default field of view (FOV) value. + * Type: float
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DEFAULT_FOV, value) + */ + public static final String SETTING_DEFAULT_FOV = "VRDefaultFOV"; + + /** + * An AppSettings parameter that set the default aspect ratio. + * Type: float
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DEFAULT_ASPECT_RATIO, value) + */ + public static final String SETTING_DEFAULT_ASPECT_RATIO = "VRDefaultAspectRatio"; + + /** + * An AppSettings parameter that specifies the underlying VR API. Possible values are:
+ *

+ * Type: int
+ * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_VRAPI, value) + + */ + public static final String SETTING_VRAPI = "VRAPI"; + + /** + * The identifier of the OpenVR system. + * @see #SETTING_VRAPI + */ + public static final int SETTING_VRAPI_OPENVR_VALUE = 1; + + /** + * The identifier of the OSVR system. + * @see #SETTING_VRAPI + */ + public static final int SETTING_VRAPI_OSVR_VALUE = 2; + + /** + * The identifier of the OpenVR from LWJGL system. + * @see #SETTING_VRAPI + */ + public static final int SETTING_VRAPI_OPENVR_LWJGL_VALUE = 3; + + + +} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/OSVR.java b/jme3-vr/src/main/java/com/jme3/input/vr/OSVR.java index 47ed23327..515e57d17 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/OSVR.java +++ b/jme3-vr/src/main/java/com/jme3/input/vr/OSVR.java @@ -9,6 +9,7 @@ https://github.com/sensics/OSVR-RenderManager/blob/master/examples/RenderManager */ package com.jme3.input.vr; +import com.jme3.app.VRAppState; import com.jme3.app.VRApplication; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; @@ -110,14 +111,14 @@ public class OSVR implements VRAPI { boolean initSuccess = false; boolean flipEyes = false; - private VRApplication application = null; + private VRAppState app = null; /** - * Create a new OSVR system attached to the given application. - * @param application the application to which the input is attached. + * Create a new OSVR system attached to the given {@link VRAppState app state}. + * @param app the app state to which the input is attached. */ - public OSVR(VRApplication application){ - this.application = application; + public OSVR(VRAppState app){ + this.app = app; } /** @@ -149,7 +150,7 @@ public class OSVR implements VRAPI { hmdPose.setAutoSynch(false); context = OsvrClientKitLibrary.osvrClientInit(defaultJString, 0); - VRinput = new OSVRInput(application); + VRinput = new OSVRInput(app); initSuccess = context != null && VRinput.init(); if( initSuccess ) { PointerByReference grabDisplay = new PointerByReference(); @@ -462,8 +463,8 @@ public class OSVR implements VRAPI { } @Override - public VRApplication getApplication() { - return application; + public VRAppState getVRAppState() { + return app; } } diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/OSVRInput.java b/jme3-vr/src/main/java/com/jme3/input/vr/OSVRInput.java index 23f51655b..85fc9a931 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/OSVRInput.java +++ b/jme3-vr/src/main/java/com/jme3/input/vr/OSVRInput.java @@ -7,6 +7,7 @@ package com.jme3.input.vr; import java.util.logging.Logger; +import com.jme3.app.VRAppState; import com.jme3.app.VRApplication; import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; @@ -63,7 +64,7 @@ public class OSVRInput implements VRInputAPI { private static final Vector2f lastCallAxis[] = new Vector2f[16]; private static float axisMultiplier = 1f; - private VRApplication application = null; + private VRAppState app = null; /** * Get the system String that identifies a controller. @@ -90,11 +91,11 @@ public class OSVRInput implements VRInputAPI { /** - * Create a new OSVR input attached to the given application. - * @param application the application to which the input is attached. + * Create a new OSVR input attached to the given {@link VRAppState app state}. + * @param app the app state to which the input is attached. */ - public OSVRInput(VRApplication application){ - this.application = application; + public OSVRInput(VRAppState app){ + this.app = app; } @@ -166,7 +167,7 @@ public class OSVRInput implements VRInputAPI { private OSVR_ClientInterface getInterface(byte[] str) { PointerByReference pbr = new PointerByReference(); - OsvrClientKitLibrary.osvrClientGetInterface((OsvrClientKitLibrary.OSVR_ClientContext)application.getVRHardware().getVRSystem(), str, pbr); + OsvrClientKitLibrary.osvrClientGetInterface((OsvrClientKitLibrary.OSVR_ClientContext)app.getVRHardware().getVRSystem(), str, pbr); return new OSVR_ClientInterface(pbr.getValue()); } @@ -302,9 +303,9 @@ public class OSVRInput implements VRInputAPI { @Override public Quaternion getFinalObserverRotation(int index) { - VRViewManager vrvm = application.getVRViewManager(); + VRViewManager vrvm = app.getVRViewManager(); if( vrvm == null || isInputDeviceTracking(index) == false ) return null; - Object obs = application.getObserver(); + Object obs = app.getObserver(); if( obs instanceof Camera ) { tempq.set(((Camera)obs).getRotation()); } else { @@ -315,9 +316,9 @@ public class OSVRInput implements VRInputAPI { @Override public Vector3f getFinalObserverPosition(int index) { - VRViewManager vrvm = application.getVRViewManager(); + VRViewManager vrvm = app.getVRViewManager(); if( vrvm == null || isInputDeviceTracking(index) == false ) return null; - Object obs = application.getObserver(); + Object obs = app.getObserver(); Vector3f pos = getPosition(index); if( obs instanceof Camera ) { ((Camera)obs).getRotation().mult(pos, pos); @@ -350,8 +351,8 @@ public class OSVRInput implements VRInputAPI { @Override - public VRApplication getApplication() { - return application; + public VRAppState getVRAppState() { + return app; } diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/OpenVR.java b/jme3-vr/src/main/java/com/jme3/input/vr/OpenVR.java index 35e16285c..3e7ff985d 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/OpenVR.java +++ b/jme3-vr/src/main/java/com/jme3/input/vr/OpenVR.java @@ -5,6 +5,7 @@ */ package com.jme3.input.vr; +import com.jme3.app.VRAppState; import com.jme3.app.VRApplication; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; @@ -83,14 +84,15 @@ public class OpenVR implements VRAPI { private static long frameCount; private static OpenVRInput VRinput; - private VRApplication application = null; + private VRAppState app = null; /** - * Create a new OpenVR system attached to the given application. - * @param application the application to which the input is attached. + * Create a new OpenVR system + * attached to the given {@link VRAppState VR app state}. + * @param appState the VR app state to which the api is attached. */ - public OpenVR(VRApplication application){ - this.application = application; + public OpenVR(VRAppState appState){ + this.app = appState; } @Override @@ -178,7 +180,7 @@ public class OpenVR implements VRAPI { } // init controllers for the first time - VRinput = new OpenVRInput(application); + VRinput = new OpenVRInput(app); VRinput.init(); VRinput.updateConnectedControllers(); @@ -204,7 +206,7 @@ public class OpenVR implements VRAPI { if(compositorFunctions != null && hmdErrorStore.getValue() == 0 ){ compositorFunctions.setAutoSynch(false); compositorFunctions.read(); - if( application.isSeatedExperience() ) { + if( app.isSeatedExperience() ) { compositorFunctions.SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated); } else { compositorFunctions.SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding); @@ -354,7 +356,7 @@ public class OpenVR implements VRAPI { frameCount = nowCount; vrsystemFunctions.GetDeviceToAbsoluteTrackingPose.apply( - application.isSeatedExperience()?JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated: + app.isSeatedExperience()?JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated: JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding, fSecondsUntilPhotons, hmdTrackedDevicePoseReference, JOpenVRLibrary.k_unMaxTrackedDeviceCount); } @@ -371,7 +373,7 @@ public class OpenVR implements VRAPI { VRInput._updateConnectedControllers(); }*/ //update controllers pose information - application.getVRinput().updateControllerStates(); + app.getVRinput().updateControllerStates(); // read pose data from native for (int nDevice = 0; nDevice < JOpenVRLibrary.k_unMaxTrackedDeviceCount; ++nDevice ){ @@ -445,7 +447,7 @@ public class OpenVR implements VRAPI { @Override public Vector3f getSeatedToAbsolutePosition() { - if( application.isSeatedExperience() == false ) return Vector3f.ZERO; + if( app.isSeatedExperience() == false ) return Vector3f.ZERO; if( hmdSeatToStand == null ) { hmdSeatToStand = new Vector3f(); HmdMatrix34_t mat = vrsystemFunctions.GetSeatedZeroPoseToStandingAbsoluteTrackingPose.apply(); @@ -524,8 +526,8 @@ public class OpenVR implements VRAPI { } @Override - public VRApplication getApplication() { - return application; + public VRAppState getVRAppState() { + return app; } } diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/OpenVRInput.java b/jme3-vr/src/main/java/com/jme3/input/vr/OpenVRInput.java index e2df488ea..cb432b3da 100644 --- a/jme3-vr/src/main/java/com/jme3/input/vr/OpenVRInput.java +++ b/jme3-vr/src/main/java/com/jme3/input/vr/OpenVRInput.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.app.VRAppState; import com.jme3.app.VRApplication; import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; @@ -97,7 +98,7 @@ public class OpenVRInput implements VRInputAPI { private final Quaternion tempq = new Quaternion(); - private VRApplication application; + private VRAppState app; private List trackedControllers = null; @@ -105,8 +106,8 @@ public class OpenVRInput implements VRInputAPI { * Create a new OpenVR input attached to the given application. * @param application the application to which the input is attached. */ - public OpenVRInput(VRApplication application){ - this.application = application; + public OpenVRInput(VRAppState appState){ + this.app = appState; } @Override @@ -296,7 +297,7 @@ public class OpenVRInput implements VRInputAPI { @Override public boolean isInputFocused() { - return ((VR_IVRSystem_FnTable)application.getVRHardware().getVRSystem()).IsInputFocusCapturedByAnotherProcess.apply() == 0; + return ((VR_IVRSystem_FnTable)app.getVRHardware().getVRSystem()).IsInputFocusCapturedByAnotherProcess.apply() == 0; } @Override @@ -326,9 +327,9 @@ public class OpenVRInput implements VRInputAPI { @Override public Quaternion getFinalObserverRotation(int index) { - VRViewManager vrvm = application.getVRViewManager(); + VRViewManager vrvm = app.getVRViewManager(); if( vrvm == null || isInputDeviceTracking(index) == false ) return null; - Object obs = application.getObserver(); + Object obs = app.getObserver(); if( obs instanceof Camera ) { tempq.set(((Camera)obs).getRotation()); } else { @@ -339,9 +340,9 @@ public class OpenVRInput implements VRInputAPI { @Override public Vector3f getFinalObserverPosition(int index) { - VRViewManager vrvm = application.getVRViewManager(); + VRViewManager vrvm = app.getVRViewManager(); if( vrvm == null || isInputDeviceTracking(index) == false ) return null; - Object obs = application.getObserver(); + Object obs = app.getObserver(); Vector3f pos = getPosition(index); if( obs instanceof Camera ) { ((Camera)obs).getRotation().mult(pos, pos); @@ -354,9 +355,9 @@ public class OpenVRInput implements VRInputAPI { @Override public void triggerHapticPulse(int controllerIndex, float seconds) { - if( application.isInVR() == false || isInputDeviceTracking(controllerIndex) == false ) return; + if( app.isInVR() == false || isInputDeviceTracking(controllerIndex) == false ) return; // apparently only axis ID of 0 works - ((VR_IVRSystem_FnTable)application.getVRHardware().getVRSystem()).TriggerHapticPulse.apply(OpenVRInput.controllerIndex[controllerIndex], + ((VR_IVRSystem_FnTable)app.getVRHardware().getVRSystem()).TriggerHapticPulse.apply(OpenVRInput.controllerIndex[controllerIndex], 0, (short)Math.round(3f * seconds / 1e-3f)); } @@ -365,13 +366,13 @@ public class OpenVRInput implements VRInputAPI { logger.config("Updating connected controllers."); controllerCount = 0; for(int i=0;i 0f && posMode != POSITIONING_MODE.MANUAL ) { // mix pos & dir with current pos & dir @@ -142,7 +159,7 @@ public class VRGuiManager { protected void positionGuiNow(float tpf) { wantsReposition = false; - if( application.isInVR() == false ) return; + if( app.isInVR() == false ) return; guiQuadNode.setLocalScale(guiDistance * guiScale * 4f, 4f * guiDistance * guiScale, 1f); switch( posMode ) { @@ -158,7 +175,7 @@ public class VRGuiManager { break; case AUTO_OBSERVER_POS_CAM_ROTATION: - Object obs = application.getObserver(); + Object obs = app.getObserver(); if( obs != null ) { if( obs instanceof Camera ) { positionTo(((Camera)obs).getLocation(), camLeft.getRotation(), tpf); @@ -171,7 +188,7 @@ public class VRGuiManager { break; case AUTO_OBSERVER_ALL: case AUTO_OBSERVER_ALL_CAMHEIGHT: - obs = application.getObserver(); + obs = app.getObserver(); if( obs != null ) { Quaternion q; if( obs instanceof Camera ) { @@ -230,7 +247,7 @@ public class VRGuiManager { } protected void setupGui(Camera leftcam, Camera rightcam, ViewPort left, ViewPort right) { - if( application.hasTraditionalGUIOverlay() ) { + if( app.hasTraditionalGUIOverlay() ) { camLeft = leftcam; camRight = rightcam; Spatial guiScene = getGuiQuad(camLeft); @@ -259,6 +276,7 @@ public class VRGuiManager { private Node guiQuadNode; private ViewPort offView; private Texture2D guiTexture; + private Spatial getGuiQuad(Camera sourceCam){ if( guiQuadNode == null ) { Vector2f guiCanvasSize = getCanvasSize(); @@ -287,7 +305,11 @@ public class VRGuiManager { offView.setOutputFrameBuffer(offBuffer); // setup framebuffer's scene - offView.attachScene(application.getGuiNode()); + Iterator spatialIter = application.getGuiViewPort().getScenes().iterator(); + while(spatialIter.hasNext()){ + offView.attachScene(spatialIter.next()); + } + if( useCurvedSurface ) { guiQuad = (Geometry)application.getAssetManager().loadModel("Common/Util/gui_mesh.j3o"); diff --git a/jme3-vr/src/main/java/jmevr/util/VRMouseManager.java b/jme3-vr/src/main/java/jmevr/util/VRMouseManager.java index 38fc705eb..5dbcd04ac 100644 --- a/jme3-vr/src/main/java/jmevr/util/VRMouseManager.java +++ b/jme3-vr/src/main/java/jmevr/util/VRMouseManager.java @@ -5,9 +5,15 @@ */ package jmevr.util; +import java.util.logging.Level; import java.util.logging.Logger; +import org.lwjgl.glfw.GLFW; + +import com.jme3.app.Application; +import com.jme3.app.VRAppState; import com.jme3.app.VRApplication; +import com.jme3.app.state.AppState; import com.jme3.input.MouseInput; import com.jme3.input.controls.AnalogListener; import com.jme3.input.lwjgl.GlfwMouseInputVR; @@ -16,6 +22,7 @@ import com.jme3.material.RenderState.BlendMode; import com.jme3.math.Vector2f; import com.jme3.scene.Node; import com.jme3.system.AppSettings; +import com.jme3.system.lwjgl.LwjglWindow; import com.jme3.system.lwjgl.LwjglWindowVR; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; @@ -29,8 +36,9 @@ public class VRMouseManager { private static final Logger logger = Logger.getLogger(VRMouseManager.class.getName()); - private VRApplication application = null; - + private Application application = null; + private VRAppState app = null; + private final int AVERAGE_AMNT = 4; private int avgCounter; @@ -48,16 +56,20 @@ public class VRMouseManager { return amt / arr.length; } - public VRMouseManager(VRApplication application){ - this.application = application; + public VRMouseManager(){ } /** - * Get the VR application to which this mouse manager is attached. - * @return the VR application to which this mouse manager is attached. + * Attach the mouse manager to an app state and an Application. + * The application has to be the one that the app state is attached. + * This method should be called from the {@link AppState#initialize(com.jme3.app.state.AppStateManager, Application) initialize} + * method of the {@link AppState} instance. + * @param app the VR app state that this manager is attached to. + * @param application the application to whitch the app state is attcached. */ - public VRApplication getApplication(){ - return application; + public void attach(VRAppState app, Application application){ + this.app = app; + this.application = application; } protected void init() { @@ -73,6 +85,8 @@ public class VRMouseManager { ((GlfwMouseInputVR)mi).hideActiveCursor(); } centerMouse(); + + logger.config("Initialized VR mouse manager [SUCCESS]"); } public void setThumbstickMode(boolean set) { @@ -101,7 +115,7 @@ public class VRMouseManager { } public void setImage(String texture) { - if( application.isInVR() == false ){ + if( app.isInVR() == false ){ Texture tex = application.getAssetManager().loadTexture(texture); mouseImage.setTexture(application.getAssetManager(), (Texture2D)tex, true); ySize = tex.getImage().getHeight(); @@ -123,14 +137,14 @@ public class VRMouseManager { public void updateAnalogAsMouse(int inputIndex, AnalogListener mouseListener, String mouseXName, String mouseYName, float tpf) { // got a tracked controller to use as the "mouse" - if( application.isInVR() == false || - application.getVRinput() == null || - application.getVRinput().isInputDeviceTracking(inputIndex) == false ) return; + if( app.isInVR() == false || + app.getVRinput() == null || + app.getVRinput().isInputDeviceTracking(inputIndex) == false ) return; Vector2f tpDelta; if( thumbstickMode ) { - tpDelta = application.getVRinput().getAxis(inputIndex, VRInputType.ViveTrackpadAxis); + tpDelta = app.getVRinput().getAxis(inputIndex, VRInputType.ViveTrackpadAxis); } else { - tpDelta = application.getVRinput().getAxisDeltaSinceLastCall(inputIndex, VRInputType.ViveTrackpadAxis); + tpDelta = app.getVRinput().getAxisDeltaSinceLastCall(inputIndex, VRInputType.ViveTrackpadAxis); } float Xamount = (float)Math.pow(Math.abs(tpDelta.x) * sensitivity, acceleration); float Yamount = (float)Math.pow(Math.abs(tpDelta.y) * sensitivity, acceleration); @@ -147,7 +161,7 @@ public class VRMouseManager { lastYmv[index] = Yamount * 133f; cursorPos.x -= avg(lastXmv); cursorPos.y -= avg(lastYmv); - Vector2f maxsize = application.getVRGUIManager().getCanvasSize(); + Vector2f maxsize = app.getVRGUIManager().getCanvasSize(); if( cursorPos.x > maxsize.x ) cursorPos.x = maxsize.x; if( cursorPos.x < 0f ) cursorPos.x = 0f; if( cursorPos.y > maxsize.y ) cursorPos.y = maxsize.y; @@ -156,7 +170,7 @@ public class VRMouseManager { } public Vector2f getCursorPosition() { - if( application.isInVR() ) { + if( app.isInVR() ) { return cursorPos; } return application.getInputManager().getCursorPosition(); @@ -164,11 +178,11 @@ public class VRMouseManager { public void centerMouse() { // set mouse in center of the screen if newly added - Vector2f size = application.getVRGUIManager().getCanvasSize(); + Vector2f size = app.getVRGUIManager().getCanvasSize(); MouseInput mi = application.getContext().getMouseInput(); AppSettings as = application.getContext().getSettings(); if( mi instanceof GlfwMouseInputVR ) ((GlfwMouseInputVR)mi).setCursorPosition((int)(as.getWidth() / 2f), (int)(as.getHeight() / 2f)); - if( application.isInVR() ) { + if( app.isInVR() ) { cursorPos.x = size.x / 2f; cursorPos.y = size.y / 2f; recentCenterCount = 2; @@ -180,11 +194,13 @@ public class VRMouseManager { if( application.getInputManager().isCursorVisible() ) { if( mouseImage.getParent() == null ) { - application.getGuiNode().attachChild(mouseImage); + + application.getGuiViewPort().attachScene(mouseImage); centerMouse(); // the "real" mouse pointer should stay hidden - org.lwjgl.glfw.GLFW.glfwSetInputMode(((LwjglWindowVR)application.getContext()).getWindowHandle(), - org.lwjgl.glfw.GLFW.GLFW_CURSOR, org.lwjgl.glfw.GLFW.GLFW_CURSOR_DISABLED); + if (application.getContext() instanceof LwjglWindow){ + GLFW.glfwSetInputMode(((LwjglWindow)application.getContext()).getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); + } } // handle mouse movements, which may be in addition to (or exclusive from) tracked movement MouseInput mi = application.getContext().getMouseInput(); @@ -195,21 +211,24 @@ public class VRMouseManager { cursorPos.y += ((GlfwMouseInputVR)mi).getLastDeltaY();// * winratio.y; if( cursorPos.x < 0f ) cursorPos.x = 0f; if( cursorPos.y < 0f ) cursorPos.y = 0f; - if( cursorPos.x > application.getVRGUIManager().getCanvasSize().x ) cursorPos.x = application.getVRGUIManager().getCanvasSize().x; - if( cursorPos.y > application.getVRGUIManager().getCanvasSize().y ) cursorPos.y = application.getVRGUIManager().getCanvasSize().y; + if( cursorPos.x > app.getVRGUIManager().getCanvasSize().x ) cursorPos.x = app.getVRGUIManager().getCanvasSize().x; + if( cursorPos.y > app.getVRGUIManager().getCanvasSize().y ) cursorPos.y = app.getVRGUIManager().getCanvasSize().y; } else recentCenterCount--; ((GlfwMouseInputVR)mi).clearDeltas(); } // ok, update the cursor graphic position Vector2f currentPos = getCursorPosition(); - mouseImage.setLocalTranslation(currentPos.x, currentPos.y - ySize, application.getVRGUIManager().getGuiDistance() + 1f); + mouseImage.setLocalTranslation(currentPos.x, currentPos.y - ySize, app.getVRGUIManager().getGuiDistance() + 1f); + mouseImage.updateGeometricState(); - mouseImage.getParent().updateGeometricState(); } else if( mouseImage.getParent() != null ) { Node n = mouseImage.getParent(); mouseImage.removeFromParent(); - n.updateGeometricState(); + + if (n != null){ + n.updateGeometricState(); + } } } } diff --git a/jme3-vr/src/main/java/jmevr/util/VRViewManager.java b/jme3-vr/src/main/java/jmevr/util/VRViewManager.java index 4558c950a..4878a42ab 100644 --- a/jme3-vr/src/main/java/jmevr/util/VRViewManager.java +++ b/jme3-vr/src/main/java/jmevr/util/VRViewManager.java @@ -1,852 +1,863 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package jmevr.util; - -import com.jme3.app.VRApplication; -import com.jme3.input.vr.OSVR; -import com.jme3.input.vr.OpenVR; -import com.jme3.input.vr.VRAPI; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.post.CartoonSSAO; -import com.jme3.post.Filter; -import com.jme3.post.FilterPostProcessor; -import com.jme3.post.SceneProcessor; -import com.jme3.post.filters.FogFilter; -import com.jme3.post.filters.TranslucentBucketFilter; -import com.jme3.post.ssao.SSAOFilter; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.shadow.DirectionalLightShadowFilter; -import com.jme3.shadow.VRDirectionalLightShadowRenderer; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.system.jopenvr.OpenVRUtil; -import com.jme3.system.jopenvr.Texture_t; -import com.jme3.system.jopenvr.VRTextureBounds_t; -import com.jme3.system.lwjgl.LwjglWindow; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; -import com.jme3.ui.Picture; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.PointerByReference; - -import java.awt.GraphicsEnvironment; -import java.util.logging.Logger; - -import osvrrendermanageropengl.OSVR_RenderBufferOpenGL; -import osvrrendermanageropengl.OSVR_ViewportDescription; -import osvrrendermanageropengl.OsvrRenderManagerOpenGLLibrary; - -/** - * A VR view manager. This class enable to submit 3D views to the VR compositor. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - */ -public class VRViewManager { - - private static final Logger logger = Logger.getLogger(VRViewManager.class.getName()); - - /** - * The name of the left view. - */ - public final static String LEFT_VIEW_NAME = "Left View"; - - /** - * The name of the right view. - */ - public final static String RIGHT_VIEW_NAME = "Right View"; - - private final VRApplication app; - - private Camera leftCamera; - private ViewPort leftViewport; - private FilterPostProcessor leftPostProcessor; - private Texture2D leftEyeTexture; - private Texture2D leftEyeDepth; - - private Camera rightCamera; - private ViewPort rightViewport; - private FilterPostProcessor rightPostProcessor; - private Texture2D rightEyeTexture; - private Texture2D rightEyeDepth; - - // OpenVR values - private VRTextureBounds_t leftTextureBounds; - private Texture_t leftTextureType; - - private VRTextureBounds_t rightTextureBounds; - private Texture_t rightTextureType; - - // OSVR values - OSVR_RenderBufferOpenGL.ByValue[] osvr_renderBuffer; - OSVR_ViewportDescription.ByValue osvr_viewDescFull; - OSVR_ViewportDescription.ByValue osvr_viewDescLeft; - OSVR_ViewportDescription.ByValue osvr_viewDescRight; - Pointer osvr_rmBufferState; - - //private static boolean useCustomDistortion; - private float heightAdjustment; - - private Texture2D dualEyeTex; - - private final PointerByReference grabRBS = new PointerByReference(); - - private float resMult = 1f; - - //final & temp values for camera calculations - private final Vector3f finalPosition = new Vector3f(); - private final Quaternion finalRotation = new Quaternion(); - private final Vector3f hmdPos = new Vector3f(); - private final Quaternion hmdRot = new Quaternion(); - - /** - * Create a new VR view manager attached to the given {@link VRApplication VR application}. - * @param application the {@link VRApplication VR application} to which this manager is linked. - */ - public VRViewManager(VRApplication application){ - this.app = application; - } - - /** - * Get the {@link Camera camera} attached to the left eye. - * @return the {@link Camera camera} attached to the left eye. - * @see #getRightCamera() - */ - public Camera getLeftCamera() { - return leftCamera; - } - - /** - * Get the {@link Camera camera} attached to the right eye. - * @return the {@link Camera camera} attached to the right eye. - * @see #getLeftCamera() - */ - public Camera getRightCamera() { - return rightCamera; - } - - /** - * Get the {@link ViewPort viewport} attached to the left eye. - * @return the {@link ViewPort viewport} attached to the left eye. - * @see #getRightViewport() - */ - public ViewPort getLeftViewport() { - return leftViewport; - } - - /** - * Get the {@link ViewPort viewport} attached to the right eye. - * @return the {@link ViewPort viewport} attached to the right eye. - * @see #getLeftViewport() - */ - public ViewPort getRightViewport() { - return rightViewport; - } - - /** - * Get the identifier of the left eye texture. - * @return the identifier of the left eye texture. - * @see #getRightTexId() - * @see #getFullTexId() - */ - private int getLeftTexId() { - return (int)leftEyeTexture.getImage().getId(); - } - - /** - * Get the identifier of the right eye texture. - * @return the identifier of the right eye texture. - * @see #getLeftTexId() - * @see #getFullTexId() - */ - private int getRightTexId() { - return (int)rightEyeTexture.getImage().getId(); - } - - /** - * Get the identifier of the full (dual eye) texture. - * @return the identifier of the full (dual eye) texture. - * @see #getLeftTexId() - * @see #getRightTexId() - */ - private int getFullTexId() { - return (int)dualEyeTex.getImage().getId(); - } - - /** - * Get the height adjustment to apply to the cameras before rendering. - * @return the height adjustment to apply to the cameras before rendering. - * @see #setHeightAdjustment(float) - */ - public float getHeightAdjustment() { - return heightAdjustment; - } - - /** - * Set the height adjustment to apply to the cameras before rendering. - * @param amount the height adjustment to apply to the cameras before rendering. - * @see #getHeightAdjustment() - */ - public void setHeightAdjustment(float amount) { - heightAdjustment = amount; - } - - /** - * Get the resolution multiplier. - * @return the resolution multiplier. - * @see #setResolutionMultiplier(float) - */ - public float getResolutionMuliplier() { - return resMult; - } - - /** - * Set the resolution multiplier. - * @param resMult the resolution multiplier. - * @see #getResolutionMuliplier() - */ - public void setResolutionMultiplier(float resMult) { - this.resMult = resMult; - } - - /** - * Initialize the system binds of the textures. - */ - private void initTextureSubmitStructs() { - leftTextureType = new Texture_t(); - rightTextureType = new Texture_t(); - - - if( app.getVRHardware() instanceof OpenVR ) { - leftTextureBounds = new VRTextureBounds_t(); - rightTextureBounds = new VRTextureBounds_t(); - // left eye - leftTextureBounds.uMax = 0.5f; - leftTextureBounds.uMin = 0f; - leftTextureBounds.vMax = 1f; - leftTextureBounds.vMin = 0f; - leftTextureBounds.setAutoSynch(false); - leftTextureBounds.setAutoRead(false); - leftTextureBounds.setAutoWrite(false); - leftTextureBounds.write(); - // right eye - rightTextureBounds.uMax = 1f; - rightTextureBounds.uMin = 0.5f; - rightTextureBounds.vMax = 1f; - rightTextureBounds.vMin = 0f; - rightTextureBounds.setAutoSynch(false); - rightTextureBounds.setAutoRead(false); - rightTextureBounds.setAutoWrite(false); - rightTextureBounds.write(); - // texture type - // FIXME: Synchronize with JMonkey given texture (at this time is linear but was Gamma with phr00t implementation) - leftTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Gamma; - //leftTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Linear; - leftTextureType.eType = JOpenVRLibrary.ETextureType.ETextureType_TextureType_OpenGL; - leftTextureType.setAutoSynch(false); - leftTextureType.setAutoRead(false); - leftTextureType.setAutoWrite(false); - leftTextureType.handle = -1; - // FIXME: Synchronize with JMonkey given texture (at this time is linear but was Gamma with phr00t implementation) - rightTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Gamma; - //rightTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Linear; - rightTextureType.eType = JOpenVRLibrary.ETextureType.ETextureType_TextureType_OpenGL; - rightTextureType.setAutoSynch(false); - rightTextureType.setAutoRead(false); - rightTextureType.setAutoWrite(false); - rightTextureType.handle = -1; - - - logger.config("Init eyes native texture binds"); - logger.config(" Left eye texture"); - logger.config(" address: "+leftTextureType.getPointer()); - logger.config(" size: "+leftTextureType.size()+" bytes"); - logger.config(" color space: "+OpenVRUtil.getEColorSpaceString(leftTextureType.eColorSpace)); - logger.config(" type: "+OpenVRUtil.getETextureTypeString(leftTextureType.eType)); - logger.config(" auto read: "+leftTextureType.getAutoRead()); - logger.config(" auto write: "+leftTextureType.getAutoWrite()); - logger.config(" handle address: "+leftTextureType.handle); - logger.config(" handle value: "+leftTextureType.handle); - logger.config(""); - logger.config(" Right eye texture"); - logger.config(" address: "+rightTextureType.getPointer()); - logger.config(" size: "+rightTextureType.size()+" bytes"); - logger.config(" color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); - logger.config(" type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); - logger.config(" auto read: "+rightTextureType.getAutoRead()); - logger.config(" auto write: "+rightTextureType.getAutoWrite()); - logger.config(" handle address: "+rightTextureType.handle); - logger.config(" handle value: "+rightTextureType.handle); - - - } else if( app.getVRHardware() instanceof OSVR ) { - // must be OSVR - osvr_renderBuffer = new OSVR_RenderBufferOpenGL.ByValue[2]; - osvr_renderBuffer[OSVR.EYE_LEFT] = new OSVR_RenderBufferOpenGL.ByValue(); - osvr_renderBuffer[OSVR.EYE_RIGHT] = new OSVR_RenderBufferOpenGL.ByValue(); - osvr_renderBuffer[OSVR.EYE_LEFT].setAutoSynch(false); - osvr_renderBuffer[OSVR.EYE_RIGHT].setAutoSynch(false); - osvr_viewDescFull = new OSVR_ViewportDescription.ByValue(); - osvr_viewDescFull.setAutoSynch(false); - osvr_viewDescFull.left = osvr_viewDescFull.lower = 0.0; - osvr_viewDescFull.width = osvr_viewDescFull.height = 1.0; - osvr_viewDescLeft = new OSVR_ViewportDescription.ByValue(); - osvr_viewDescLeft.setAutoSynch(false); - osvr_viewDescLeft.left = osvr_viewDescLeft.lower = 0.0; - osvr_viewDescLeft.width = 0.5; - osvr_viewDescLeft.height = 1.0; - osvr_viewDescRight = new OSVR_ViewportDescription.ByValue(); - osvr_viewDescRight.setAutoSynch(false); - osvr_viewDescRight.left = 0.5; - osvr_viewDescRight.lower = 0.0; - osvr_viewDescRight.width = 0.5; - osvr_viewDescRight.height = 1.0; - osvr_viewDescRight.write(); - osvr_viewDescLeft.write(); - osvr_viewDescFull.write(); - osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = -1; - osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = -1; - osvr_renderBuffer[OSVR.EYE_RIGHT].depthStencilBufferName = -1; - osvr_renderBuffer[OSVR.EYE_RIGHT].colorBufferName = -1; - } - } - - /** - * Register the OSVR OpenGL buffer. - * @param buf the OSVR OpenGL buffer. - */ - private void registerOSVRBuffer(OSVR_RenderBufferOpenGL.ByValue buf) { - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerStartRegisterRenderBuffers(grabRBS); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerRegisterRenderBufferOpenGL(grabRBS.getValue(), buf); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerFinishRegisterRenderBuffers(((OSVR)app.getVRHardware()).getCompositor(), grabRBS.getValue(), (byte)0); - } - - /** - * Send the textures to the two eyes. - */ - public void sendTextures() { - if( app.isInVR() ) { - VRAPI api = app.getVRHardware(); - if( api.getCompositor() != null ) { - // using the compositor... - int errl = 0, errr = 0; - if( app.isInstanceVRRendering() ) { - if( leftTextureType.handle == -1 || leftTextureType.handle != getFullTexId() ) { - leftTextureType.handle = getFullTexId(); - if( leftTextureType.handle != -1 ) { - leftTextureType.write(); - if( api instanceof OSVR ) { - osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = leftTextureType.handle; - osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = dualEyeTex.getImage().getId(); - osvr_renderBuffer[OSVR.EYE_LEFT].write(); - registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_LEFT]); - } - } - } else { - if( api instanceof OpenVR ) { - int submitFlag = JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default; - errr = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, leftTextureType, rightTextureBounds, submitFlag); - errl = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, leftTextureType, leftTextureBounds, submitFlag); - } else if( api instanceof OSVR ) { - ((OSVR)api).handleRenderBufferPresent(osvr_viewDescLeft, osvr_viewDescRight, - osvr_renderBuffer[OSVR.EYE_LEFT], osvr_renderBuffer[OSVR.EYE_LEFT]); - } - } - } else if( leftTextureType.handle == -1 || rightTextureType.handle == -1 || - leftTextureType.handle != getLeftTexId() || rightTextureType.handle != getRightTexId() ) { - leftTextureType.handle = getLeftTexId(); - if( leftTextureType.handle != -1 ) { - logger.fine("Writing Left texture to native memory at " + leftTextureType.getPointer()); - leftTextureType.write(); - if( api instanceof OSVR ) { - osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = leftTextureType.handle; - if( leftEyeDepth != null ) osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = leftEyeDepth.getImage().getId(); - osvr_renderBuffer[OSVR.EYE_LEFT].write(); - registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_LEFT]); - } - } - rightTextureType.handle = getRightTexId(); - if( rightTextureType.handle != -1 ) { - logger.fine("Writing Right texture to native memory at " + leftTextureType.getPointer()); - rightTextureType.write(); - if( api instanceof OSVR ) { - osvr_renderBuffer[OSVR.EYE_RIGHT].colorBufferName = rightTextureType.handle; - if( rightEyeDepth != null ) osvr_renderBuffer[OSVR.EYE_RIGHT].depthStencilBufferName = rightEyeDepth.getImage().getId(); - osvr_renderBuffer[OSVR.EYE_RIGHT].write(); - registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_RIGHT]); - } - } - } else { - if( api instanceof OpenVR ) { - errl = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, leftTextureType, null, - JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default); - errr = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, rightTextureType, null, - JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default); - } else if( api instanceof OSVR ) { - ((OSVR)api).handleRenderBufferPresent(osvr_viewDescFull, osvr_viewDescFull, - osvr_renderBuffer[OSVR.EYE_LEFT], osvr_renderBuffer[OSVR.EYE_RIGHT]); - } - } - - if( errl != 0 ){ - logger.severe("Submit to left compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); - logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(leftTextureType.eColorSpace)); - logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(leftTextureType.eType)); - logger.severe(" Texture handle: "+leftTextureType.handle); - - logger.severe(" Left eye texture "+leftEyeTexture.getName()+" ("+leftEyeTexture.getImage().getId()+")"); - logger.severe(" Type: "+leftEyeTexture.getType()); - logger.severe(" Size: "+leftEyeTexture.getImage().getWidth()+"x"+leftEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: "+leftEyeTexture.getImage().getDepth()); - logger.severe(" Image format: "+leftEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: "+leftEyeTexture.getImage().getColorSpace()); - - } - - if( errr != 0 ){ - logger.severe("Submit to right compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); - logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); - logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); - logger.severe(" Texture handle: "+rightTextureType.handle); - - logger.severe(" Right eye texture "+rightEyeTexture.getName()+" ("+rightEyeTexture.getImage().getId()+")"); - logger.severe(" Type: "+rightEyeTexture.getType()); - logger.severe(" Size: "+rightEyeTexture.getImage().getWidth()+"x"+rightEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: "+rightEyeTexture.getImage().getDepth()); - logger.severe(" Image format: "+rightEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: "+rightEyeTexture.getImage().getColorSpace()); - } - } - } - } - - - /** - * Initialize the VR view manager. - */ - public void initialize() { - - logger.config("Initializing VR view manager."); - - initTextureSubmitStructs(); - setupCamerasAndViews(); - setupVRScene(); - moveScreenProcessingToEyes(); - if( app.hasTraditionalGUIOverlay() ) { - - app.getVRMouseManager().init(); - - // update the pose to position the gui correctly on start - update(0f); - app.getVRGUIManager().positionGui(); - } - // if we are OSVR, our primary mirror window needs to be the same size as the render manager's output... - if( app.getVRHardware() instanceof OSVR ) { - int origWidth = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth(); - int origHeight = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getHeight(); - long window = ((LwjglWindow)app.getContext()).getWindowHandle(); - Vector2f windowSize = new Vector2f(); - ((OSVR)app.getVRHardware()).getRenderSize(windowSize); - windowSize.x = Math.max(windowSize.x * 2f, leftCamera.getWidth()); - org.lwjgl.glfw.GLFW.glfwSetWindowSize(window, (int)windowSize.x, (int)windowSize.y); - app.getContext().getSettings().setResolution((int)windowSize.x, (int)windowSize.y); - app.reshape((int)windowSize.x, (int)windowSize.y); - org.lwjgl.glfw.GLFW.glfwSetWindowPos(window, origWidth - (int)windowSize.x, 32); - - org.lwjgl.glfw.GLFW.glfwFocusWindow(window); - - org.lwjgl.glfw.GLFW.glfwSetCursorPos(window, origWidth / 2.0, origHeight / 2.0); - } - } - - /** - * Prepare the size of the given {@link Camera camera} to adapt it to the underlying rendering context. - * @param cam the {@link Camera camera} to prepare. - * @param xMult the camera width multiplier. - */ - private void prepareCameraSize(Camera cam, float xMult) { - Vector2f size = new Vector2f(); - VRAPI vrhmd = app.getVRHardware(); - - if( vrhmd == null ) { - size.x = 1280f; - size.y = 720f; - } else { - vrhmd.getRenderSize(size); - } - - if( size.x < app.getContext().getSettings().getWidth() ) { - size.x = app.getContext().getSettings().getWidth(); - } - if( size.y < app.getContext().getSettings().getHeight() ) { - size.y = app.getContext().getSettings().getHeight(); - } - - if( app.isInstanceVRRendering() ) size.x *= 2f; - - // other adjustments - size.x *= xMult; - size.x *= resMult; - size.y *= resMult; - - if( cam.getWidth() != size.x || cam.getHeight() != size.y ) cam.resize((int)size.x, (int)size.y, false); - } - - /** - * Replaces rootNode as the main cameras scene with the distortion mesh - */ - private void setupVRScene(){ - // no special scene to setup if we are doing instancing - if( app.isInstanceVRRendering() ) { - // distortion has to be done with compositor here... we want only one pass on our end! - if( app.getContext().getSettings().isSwapBuffers() ) { - setupMirrorBuffers(app.getCamera(), dualEyeTex, true); - } - return; - } - - leftEyeTexture = (Texture2D) leftViewport.getOutputFrameBuffer().getColorBuffer().getTexture(); - rightEyeTexture = (Texture2D)rightViewport.getOutputFrameBuffer().getColorBuffer().getTexture(); - leftEyeDepth = (Texture2D) leftViewport.getOutputFrameBuffer().getDepthBuffer().getTexture(); - rightEyeDepth = (Texture2D)rightViewport.getOutputFrameBuffer().getDepthBuffer().getTexture(); -/* - logger.config("Left eye texture "+leftEyeTexture.getName()+" ("+leftEyeTexture.getImage().getId()+")"); - logger.config(" Type: "+leftEyeTexture.getType()); - logger.config(" Size: "+leftEyeTexture.getImage().getWidth()+"x"+leftEyeTexture.getImage().getHeight()); - logger.config(" Image depth: "+leftEyeTexture.getImage().getDepth()); - logger.config(" Image format: "+leftEyeTexture.getImage().getFormat()); - logger.config(" Image color space: "+leftEyeTexture.getImage().getColorSpace()); - - logger.config("Left eye depth "+leftEyeDepth.getName()+" ("+leftEyeDepth.getImage().getId()+")"); - logger.config(" Type: "+leftEyeDepth.getType()); - logger.config(" Size: "+leftEyeDepth.getImage().getWidth()+"x"+leftEyeDepth.getImage().getHeight()); - logger.config(" Image depth: "+leftEyeDepth.getImage().getDepth()); - logger.config(" Image format: "+leftEyeDepth.getImage().getFormat()); - logger.config(" Image color space: "+leftEyeDepth.getImage().getColorSpace()); - - logger.config("Right eye texture "+rightEyeTexture.getName()+" ("+rightEyeTexture.getImage().getId()+")"); - logger.config(" Type: "+rightEyeTexture.getType()); - logger.config(" Size: "+rightEyeTexture.getImage().getWidth()+"x"+rightEyeTexture.getImage().getHeight()); - logger.config(" Image depth: "+rightEyeTexture.getImage().getDepth()); - logger.config(" Image format: "+rightEyeTexture.getImage().getFormat()); - logger.config(" Image color space: "+rightEyeTexture.getImage().getColorSpace()); - - logger.config("Right eye depth "+rightEyeDepth.getName()+" ("+rightEyeDepth.getImage().getId()+")"); - logger.config(" Type: "+rightEyeDepth.getType()); - logger.config(" Size: "+rightEyeDepth.getImage().getWidth()+"x"+rightEyeDepth.getImage().getHeight()); - logger.config(" Image depth: "+rightEyeDepth.getImage().getDepth()); - logger.config(" Image format: "+rightEyeDepth.getImage().getFormat()); - logger.config(" Image color space: "+rightEyeDepth.getImage().getColorSpace()); -*/ - // main viewport is either going to be a distortion scene or nothing - // mirroring is handled by copying framebuffers - app.getViewPort().detachScene(app.getRootNode()); - app.getViewPort().detachScene(app.getGuiNode()); - - // only setup distortion scene if compositor isn't running (or using custom mesh distortion option) - if( app.getVRHardware().getCompositor() == null ) { - Node distortionScene = new Node(); - Material leftMat = new Material(app.getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); - leftMat.setTexture("Texture", leftEyeTexture); - Geometry leftEye = new Geometry("box", MeshUtil.setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Left, app)); - leftEye.setMaterial(leftMat); - distortionScene.attachChild(leftEye); - - Material rightMat = new Material(app.getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); - rightMat.setTexture("Texture", rightEyeTexture); - Geometry rightEye = new Geometry("box", MeshUtil.setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Right, app)); - rightEye.setMaterial(rightMat); - distortionScene.attachChild(rightEye); - - distortionScene.updateGeometricState(); - - app.getViewPort().attachScene(distortionScene); - - //if( useCustomDistortion ) setupFinalFullTexture(app.getViewPort().getCamera()); - } - - if( app.getContext().getSettings().isSwapBuffers() ) { - setupMirrorBuffers(app.getCamera(), leftEyeTexture, false); - } - } - - /** - * Update the VR view manager. - * This method is called by the attached {@link VRApplication VR application} and should not be called manually. - * @param tpf the time per frame. - */ - public void update(float tpf) { - - // grab the observer - Object obs = app.getObserver(); - Quaternion objRot; - Vector3f objPos; - if( obs instanceof Camera ) { - objRot = ((Camera)obs).getRotation(); - objPos = ((Camera)obs).getLocation(); - } else { - objRot = ((Spatial)obs).getWorldRotation(); - objPos = ((Spatial)obs).getWorldTranslation(); - } - // grab the hardware handle - VRAPI dev = app.getVRHardware(); - if( dev != null ) { - // update the HMD's position & orientation - dev.updatePose(); - dev.getPositionAndOrientation(hmdPos, hmdRot); - if( obs != null ) { - // update hmdPos based on obs rotation - finalRotation.set(objRot); - finalRotation.mult(hmdPos, hmdPos); - finalRotation.multLocal(hmdRot); - } - - finalizeCamera(dev.getHMDVectorPoseLeftEye(), objPos, leftCamera); - finalizeCamera(dev.getHMDVectorPoseRightEye(), objPos, rightCamera); - } else { - leftCamera.setFrame(objPos, objRot); - rightCamera.setFrame(objPos, objRot); - } - - if( app.hasTraditionalGUIOverlay() ) { - // update the mouse? - app.getVRMouseManager().update(tpf); - - // update GUI position? - if( app.getVRGUIManager().wantsReposition || app.getVRGUIManager().getPositioningMode() != VRGuiManager.POSITIONING_MODE.MANUAL ) { - app.getVRGUIManager().positionGuiNow(tpf); - app.getVRGUIManager().updateGuiQuadGeometricState(); - } - } - } - - /** - * Place the camera within the scene. - * @param eyePos the eye position. - * @param obsPosition the observer position. - * @param cam the camera to place. - */ - private void finalizeCamera(Vector3f eyePos, Vector3f obsPosition, Camera cam) { - finalRotation.mult(eyePos, finalPosition); - finalPosition.addLocal(hmdPos); - if( obsPosition != null ) finalPosition.addLocal(obsPosition); - finalPosition.y += heightAdjustment; - cam.setFrame(finalPosition, finalRotation); - } - - /** - * Handles moving filters from the main view to each eye - */ - public void moveScreenProcessingToEyes() { - if( rightViewport == null ) return; - syncScreenProcessing(app.getViewPort()); - app.getViewPort().clearProcessors(); - } - - /** - * Sets the two views to use the list of {@link SceneProcessor processors}. - * @param sourceViewport the {@link ViewPort viewport} that contains the processors to use. - */ - public void syncScreenProcessing(ViewPort sourceViewport) { - if( rightViewport == null ) return; - // setup post processing filters - if( rightPostProcessor == null ) { - rightPostProcessor = new FilterPostProcessor(app.getAssetManager()); - leftPostProcessor = new FilterPostProcessor(app.getAssetManager()); - } - // clear out all filters & processors, to start from scratch - rightPostProcessor.removeAllFilters(); - leftPostProcessor.removeAllFilters(); - leftViewport.clearProcessors(); - rightViewport.clearProcessors(); - // if we have no processors to sync, don't add the FilterPostProcessor - if( sourceViewport.getProcessors().isEmpty() ) return; - // add post processors we just made, which are empty - leftViewport.addProcessor(leftPostProcessor); - rightViewport.addProcessor(rightPostProcessor); - // go through all of the filters in the processors list - // add them to the left viewport processor & clone them to the right - for(SceneProcessor sceneProcessor : sourceViewport.getProcessors()) { - if (sceneProcessor instanceof FilterPostProcessor) { - for(Filter f : ((FilterPostProcessor)sceneProcessor).getFilterList() ) { - if( f instanceof TranslucentBucketFilter ) { - // just remove this filter, we will add it at the end manually - ((FilterPostProcessor)sceneProcessor).removeFilter(f); - } else { - leftPostProcessor.addFilter(f); - // clone to the right - Filter f2; - if(f instanceof FogFilter){ - f2 = FilterUtil.cloneFogFilter((FogFilter)f); - } else if (f instanceof CartoonSSAO ) { - f2 = new CartoonSSAO((CartoonSSAO)f); - } else if (f instanceof SSAOFilter){ - f2 = FilterUtil.cloneSSAOFilter((SSAOFilter)f); - } else if (f instanceof DirectionalLightShadowFilter){ - f2 = FilterUtil.cloneDirectionalLightShadowFilter(app.getAssetManager(), (DirectionalLightShadowFilter)f); - } else { - f2 = f; // dof, bloom, lightscattering etc. - } - rightPostProcessor.addFilter(f2); - } - } - } else if (sceneProcessor instanceof VRDirectionalLightShadowRenderer) { - // shadow processing - // TODO: make right shadow processor use same left shadow maps for performance - VRDirectionalLightShadowRenderer dlsr = (VRDirectionalLightShadowRenderer) sceneProcessor; - VRDirectionalLightShadowRenderer dlsrRight = dlsr.clone(); - dlsrRight.setLight(dlsr.getLight()); - rightViewport.getProcessors().add(0, dlsrRight); - leftViewport.getProcessors().add(0, sceneProcessor); - } - } - // make sure each has a translucent filter renderer - leftPostProcessor.addFilter(new TranslucentBucketFilter()); - rightPostProcessor.addFilter(new TranslucentBucketFilter()); - } - - private void setupCamerasAndViews() { - // get desired frustrum from original camera - Camera origCam = app.getBaseCamera(); - float fFar = origCam.getFrustumFar(); - float fNear = origCam.getFrustumNear(); - - // if we are using OSVR get the eye info here - if( app.getVRHardware() instanceof OSVR ) { - ((OSVR)app.getVRHardware()).getEyeInfo(); - } - - // restore frustrum on distortion scene cam, if needed - if( app.isInstanceVRRendering() ) { - leftCamera = origCam; - } else if( app.compositorAllowed() == false ) { - origCam.setFrustumFar(100f); - origCam.setFrustumNear(1f); - leftCamera = origCam.clone(); - prepareCameraSize(origCam, 2f); - } else { - leftCamera = origCam.clone(); - } - - leftCamera.setFrustumPerspective(app.DEFAULT_FOV, app.DEFAULT_ASPECT, fNear, fFar); - - prepareCameraSize(leftCamera, 1f); - if( app.getVRHardware() != null ) leftCamera.setProjectionMatrix(app.getVRHardware().getHMDMatrixProjectionLeftEye(leftCamera)); - //org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_SRGB); - - if( app.isInstanceVRRendering() == false ) { - leftViewport = setupViewBuffers(leftCamera, LEFT_VIEW_NAME); - rightCamera = leftCamera.clone(); - if( app.getVRHardware() != null ) rightCamera.setProjectionMatrix(app.getVRHardware().getHMDMatrixProjectionRightEye(rightCamera)); - rightViewport = setupViewBuffers(rightCamera, RIGHT_VIEW_NAME); - } else { - leftViewport = app.getViewPort(); - leftViewport.attachScene(app.getRootNode()); - rightCamera = leftCamera.clone(); - if( app.getVRHardware() != null ) rightCamera.setProjectionMatrix(app.getVRHardware().getHMDMatrixProjectionRightEye(rightCamera)); - - org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_CLIP_DISTANCE0); - - //FIXME: [jme-vr] Fix with JMonkey next release - //RenderManager._VRInstancing_RightCamProjection = camRight.getViewProjectionMatrix(); - - setupFinalFullTexture(app.getViewPort().getCamera()); - } - - // setup gui - app.getVRGUIManager().setupGui(leftCamera, rightCamera, leftViewport, rightViewport); - - if( app.getVRHardware() != null ) { - // call these to cache the results internally - app.getVRHardware().getHMDMatrixPoseLeftEye(); - app.getVRHardware().getHMDMatrixPoseRightEye(); - } - - } - - private ViewPort setupMirrorBuffers(Camera cam, Texture tex, boolean expand) { - Camera clonecam = cam.clone(); - ViewPort viewPort = app.getRenderManager().createPostView("MirrorView", clonecam); - clonecam.setParallelProjection(true); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - Picture pic = new Picture("fullscene"); - pic.setLocalTranslation(-0.75f, -0.5f, 0f); - if( expand ) { - pic.setLocalScale(3f, 1f, 1f); - } else { - pic.setLocalScale(1.5f, 1f, 1f); - } - pic.setQueueBucket(Bucket.Opaque); - pic.setTexture(app.getAssetManager(), (Texture2D)tex, false); - viewPort.attachScene(pic); - viewPort.setOutputFrameBuffer(null); - - pic.updateGeometricState(); - - return viewPort; - } - - private void setupFinalFullTexture(Camera cam) { - // create offscreen framebuffer - FrameBuffer out = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBuffer.setSrgb(true); - - //setup framebuffer's texture - dualEyeTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - dualEyeTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); - dualEyeTex.setMagFilter(Texture.MagFilter.Bilinear); - - logger.config("Dual eye texture "+dualEyeTex.getName()+" ("+dualEyeTex.getImage().getId()+")"); - logger.config(" Type: "+dualEyeTex.getType()); - logger.config(" Size: "+dualEyeTex.getImage().getWidth()+"x"+dualEyeTex.getImage().getHeight()); - logger.config(" Image depth: "+dualEyeTex.getImage().getDepth()); - logger.config(" Image format: "+dualEyeTex.getImage().getFormat()); - logger.config(" Image color space: "+dualEyeTex.getImage().getColorSpace()); - - //setup framebuffer to use texture - out.setDepthBuffer(Image.Format.Depth); - out.setColorTexture(dualEyeTex); - - ViewPort viewPort = this.app.getViewPort(); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - viewPort.setOutputFrameBuffer(out); - - } - - private ViewPort setupViewBuffers(Camera cam, String viewName){ - // create offscreen framebuffer - FrameBuffer offBufferLeft = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBufferLeft.setSrgb(true); - - //setup framebuffer's texture - Texture2D offTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - offTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); - offTex.setMagFilter(Texture.MagFilter.Bilinear); - - //setup framebuffer to use texture - offBufferLeft.setDepthBuffer(Image.Format.Depth); - offBufferLeft.setColorTexture(offTex); - - ViewPort viewPort = app.getRenderManager().createPreView(viewName, cam); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - viewPort.attachScene(this.app.getRootNode()); - //set viewport to render to offscreen framebuffer - viewPort.setOutputFrameBuffer(offBufferLeft); - return viewPort; - } -} +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ +package jmevr.util; + +import com.jme3.app.Application; +import com.jme3.app.VRAppState; +import com.jme3.app.VRApplication; +import com.jme3.app.state.AppState; +import com.jme3.input.vr.OSVR; +import com.jme3.input.vr.OpenVR; +import com.jme3.input.vr.VRAPI; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.post.CartoonSSAO; +import com.jme3.post.Filter; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.SceneProcessor; +import com.jme3.post.filters.FogFilter; +import com.jme3.post.filters.TranslucentBucketFilter; +import com.jme3.post.ssao.SSAOFilter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.shadow.VRDirectionalLightShadowRenderer; +import com.jme3.system.jopenvr.JOpenVRLibrary; +import com.jme3.system.jopenvr.OpenVRUtil; +import com.jme3.system.jopenvr.Texture_t; +import com.jme3.system.jopenvr.VRTextureBounds_t; +import com.jme3.system.lwjgl.LwjglWindow; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.ui.Picture; +import com.sun.jna.Pointer; +import com.sun.jna.ptr.PointerByReference; + +import java.awt.GraphicsEnvironment; +import java.util.Iterator; +import java.util.logging.Logger; + +import osvrrendermanageropengl.OSVR_RenderBufferOpenGL; +import osvrrendermanageropengl.OSVR_ViewportDescription; +import osvrrendermanageropengl.OsvrRenderManagerOpenGLLibrary; + +/** + * A VR view manager. This class enable to submit 3D views to the VR compositor. + * @author reden - phr00t - https://github.com/phr00t + * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org + */ +public class VRViewManager { + + private static final Logger logger = Logger.getLogger(VRViewManager.class.getName()); + + /** + * The name of the left view. + */ + public final static String LEFT_VIEW_NAME = "Left View"; + + /** + * The name of the right view. + */ + public final static String RIGHT_VIEW_NAME = "Right View"; + + private VRAppState app; + private Application application; + + private Camera leftCamera; + private ViewPort leftViewport; + private FilterPostProcessor leftPostProcessor; + private Texture2D leftEyeTexture; + private Texture2D leftEyeDepth; + + private Camera rightCamera; + private ViewPort rightViewport; + private FilterPostProcessor rightPostProcessor; + private Texture2D rightEyeTexture; + private Texture2D rightEyeDepth; + + // OpenVR values + private VRTextureBounds_t leftTextureBounds; + private Texture_t leftTextureType; + + private VRTextureBounds_t rightTextureBounds; + private Texture_t rightTextureType; + + // OSVR values + OSVR_RenderBufferOpenGL.ByValue[] osvr_renderBuffer; + OSVR_ViewportDescription.ByValue osvr_viewDescFull; + OSVR_ViewportDescription.ByValue osvr_viewDescLeft; + OSVR_ViewportDescription.ByValue osvr_viewDescRight; + Pointer osvr_rmBufferState; + + //private static boolean useCustomDistortion; + private float heightAdjustment; + + private Texture2D dualEyeTex; + + private final PointerByReference grabRBS = new PointerByReference(); + + private float resMult = 1f; + + //final & temp values for camera calculations + private final Vector3f finalPosition = new Vector3f(); + private final Quaternion finalRotation = new Quaternion(); + private final Vector3f hmdPos = new Vector3f(); + private final Quaternion hmdRot = new Quaternion(); + + /** + * Create a new VR view manager attached to the given {@link VRAppState VR app state}.
+ * in order to be used, this manager has to be attached to an app state and to an application. + */ + public VRViewManager(){ + } + + /** + * Attach this manager to the given {@link VRAppState app state} and the given {@link Application application}. + * The application has to be the one that the app state is attached. + * This method should be called from the {@link AppState#initialize(com.jme3.app.state.AppStateManager, Application) initialize} + * method of the {@link AppState} instance. + * @param app the {@link VRAppState VR app state} to which this manager is linked. + * @param application the {@link Application} which the app state is attached. + */ + public void attach(VRAppState app, Application application){ + this.app = app; + this.application = application; + } + + /** + * Get the {@link Camera camera} attached to the left eye. + * @return the {@link Camera camera} attached to the left eye. + * @see #getRightCamera() + */ + public Camera getLeftCamera() { + return leftCamera; + } + + /** + * Get the {@link Camera camera} attached to the right eye. + * @return the {@link Camera camera} attached to the right eye. + * @see #getLeftCamera() + */ + public Camera getRightCamera() { + return rightCamera; + } + + /** + * Get the {@link ViewPort viewport} attached to the left eye. + * @return the {@link ViewPort viewport} attached to the left eye. + * @see #getRightViewport() + */ + public ViewPort getLeftViewport() { + return leftViewport; + } + + /** + * Get the {@link ViewPort viewport} attached to the right eye. + * @return the {@link ViewPort viewport} attached to the right eye. + * @see #getLeftViewport() + */ + public ViewPort getRightViewport() { + return rightViewport; + } + + /** + * Get the identifier of the left eye texture. + * @return the identifier of the left eye texture. + * @see #getRightTexId() + * @see #getFullTexId() + */ + private int getLeftTexId() { + return (int)leftEyeTexture.getImage().getId(); + } + + /** + * Get the identifier of the right eye texture. + * @return the identifier of the right eye texture. + * @see #getLeftTexId() + * @see #getFullTexId() + */ + private int getRightTexId() { + return (int)rightEyeTexture.getImage().getId(); + } + + /** + * Get the identifier of the full (dual eye) texture. + * @return the identifier of the full (dual eye) texture. + * @see #getLeftTexId() + * @see #getRightTexId() + */ + private int getFullTexId() { + return (int)dualEyeTex.getImage().getId(); + } + + /** + * Get the height adjustment to apply to the cameras before rendering. + * @return the height adjustment to apply to the cameras before rendering. + * @see #setHeightAdjustment(float) + */ + public float getHeightAdjustment() { + return heightAdjustment; + } + + /** + * Set the height adjustment to apply to the cameras before rendering. + * @param amount the height adjustment to apply to the cameras before rendering. + * @see #getHeightAdjustment() + */ + public void setHeightAdjustment(float amount) { + heightAdjustment = amount; + } + + /** + * Get the resolution multiplier. + * @return the resolution multiplier. + * @see #setResolutionMultiplier(float) + */ + public float getResolutionMuliplier() { + return resMult; + } + + /** + * Set the resolution multiplier. + * @param resMult the resolution multiplier. + * @see #getResolutionMuliplier() + */ + public void setResolutionMultiplier(float resMult) { + this.resMult = resMult; + } + + /** + * Initialize the system binds of the textures. + */ + private void initTextureSubmitStructs() { + leftTextureType = new Texture_t(); + rightTextureType = new Texture_t(); + + + if( app.getVRHardware() instanceof OpenVR ) { + leftTextureBounds = new VRTextureBounds_t(); + rightTextureBounds = new VRTextureBounds_t(); + // left eye + leftTextureBounds.uMax = 0.5f; + leftTextureBounds.uMin = 0f; + leftTextureBounds.vMax = 1f; + leftTextureBounds.vMin = 0f; + leftTextureBounds.setAutoSynch(false); + leftTextureBounds.setAutoRead(false); + leftTextureBounds.setAutoWrite(false); + leftTextureBounds.write(); + // right eye + rightTextureBounds.uMax = 1f; + rightTextureBounds.uMin = 0.5f; + rightTextureBounds.vMax = 1f; + rightTextureBounds.vMin = 0f; + rightTextureBounds.setAutoSynch(false); + rightTextureBounds.setAutoRead(false); + rightTextureBounds.setAutoWrite(false); + rightTextureBounds.write(); + // texture type + // FIXME: Synchronize with JMonkey given texture (at this time is linear but was Gamma with phr00t implementation) + leftTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Gamma; + //leftTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Linear; + leftTextureType.eType = JOpenVRLibrary.ETextureType.ETextureType_TextureType_OpenGL; + leftTextureType.setAutoSynch(false); + leftTextureType.setAutoRead(false); + leftTextureType.setAutoWrite(false); + leftTextureType.handle = -1; + // FIXME: Synchronize with JMonkey given texture (at this time is linear but was Gamma with phr00t implementation) + rightTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Gamma; + //rightTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Linear; + rightTextureType.eType = JOpenVRLibrary.ETextureType.ETextureType_TextureType_OpenGL; + rightTextureType.setAutoSynch(false); + rightTextureType.setAutoRead(false); + rightTextureType.setAutoWrite(false); + rightTextureType.handle = -1; + + + logger.config("Init eyes native texture binds"); + logger.config(" Left eye texture"); + logger.config(" address: "+leftTextureType.getPointer()); + logger.config(" size: "+leftTextureType.size()+" bytes"); + logger.config(" color space: "+OpenVRUtil.getEColorSpaceString(leftTextureType.eColorSpace)); + logger.config(" type: "+OpenVRUtil.getETextureTypeString(leftTextureType.eType)); + logger.config(" auto read: "+leftTextureType.getAutoRead()); + logger.config(" auto write: "+leftTextureType.getAutoWrite()); + logger.config(" handle address: "+leftTextureType.handle); + logger.config(" handle value: "+leftTextureType.handle); + logger.config(""); + logger.config(" Right eye texture"); + logger.config(" address: "+rightTextureType.getPointer()); + logger.config(" size: "+rightTextureType.size()+" bytes"); + logger.config(" color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); + logger.config(" type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); + logger.config(" auto read: "+rightTextureType.getAutoRead()); + logger.config(" auto write: "+rightTextureType.getAutoWrite()); + logger.config(" handle address: "+rightTextureType.handle); + logger.config(" handle value: "+rightTextureType.handle); + + + } else if( app.getVRHardware() instanceof OSVR ) { + // must be OSVR + osvr_renderBuffer = new OSVR_RenderBufferOpenGL.ByValue[2]; + osvr_renderBuffer[OSVR.EYE_LEFT] = new OSVR_RenderBufferOpenGL.ByValue(); + osvr_renderBuffer[OSVR.EYE_RIGHT] = new OSVR_RenderBufferOpenGL.ByValue(); + osvr_renderBuffer[OSVR.EYE_LEFT].setAutoSynch(false); + osvr_renderBuffer[OSVR.EYE_RIGHT].setAutoSynch(false); + osvr_viewDescFull = new OSVR_ViewportDescription.ByValue(); + osvr_viewDescFull.setAutoSynch(false); + osvr_viewDescFull.left = osvr_viewDescFull.lower = 0.0; + osvr_viewDescFull.width = osvr_viewDescFull.height = 1.0; + osvr_viewDescLeft = new OSVR_ViewportDescription.ByValue(); + osvr_viewDescLeft.setAutoSynch(false); + osvr_viewDescLeft.left = osvr_viewDescLeft.lower = 0.0; + osvr_viewDescLeft.width = 0.5; + osvr_viewDescLeft.height = 1.0; + osvr_viewDescRight = new OSVR_ViewportDescription.ByValue(); + osvr_viewDescRight.setAutoSynch(false); + osvr_viewDescRight.left = 0.5; + osvr_viewDescRight.lower = 0.0; + osvr_viewDescRight.width = 0.5; + osvr_viewDescRight.height = 1.0; + osvr_viewDescRight.write(); + osvr_viewDescLeft.write(); + osvr_viewDescFull.write(); + osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = -1; + osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = -1; + osvr_renderBuffer[OSVR.EYE_RIGHT].depthStencilBufferName = -1; + osvr_renderBuffer[OSVR.EYE_RIGHT].colorBufferName = -1; + } + } + + /** + * Register the OSVR OpenGL buffer. + * @param buf the OSVR OpenGL buffer. + */ + private void registerOSVRBuffer(OSVR_RenderBufferOpenGL.ByValue buf) { + OsvrRenderManagerOpenGLLibrary.osvrRenderManagerStartRegisterRenderBuffers(grabRBS); + OsvrRenderManagerOpenGLLibrary.osvrRenderManagerRegisterRenderBufferOpenGL(grabRBS.getValue(), buf); + OsvrRenderManagerOpenGLLibrary.osvrRenderManagerFinishRegisterRenderBuffers(((OSVR)app.getVRHardware()).getCompositor(), grabRBS.getValue(), (byte)0); + } + + /** + * Send the textures to the two eyes. + */ + public void sendTextures() { + if( app.isInVR() ) { + VRAPI api = app.getVRHardware(); + if( api.getCompositor() != null ) { + // using the compositor... + int errl = 0, errr = 0; + if( app.isInstanceVRRendering() ) { + if( leftTextureType.handle == -1 || leftTextureType.handle != getFullTexId() ) { + leftTextureType.handle = getFullTexId(); + if( leftTextureType.handle != -1 ) { + leftTextureType.write(); + if( api instanceof OSVR ) { + osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = leftTextureType.handle; + osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = dualEyeTex.getImage().getId(); + osvr_renderBuffer[OSVR.EYE_LEFT].write(); + registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_LEFT]); + } + } + } else { + if( api instanceof OpenVR ) { + int submitFlag = JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default; + errr = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, leftTextureType, rightTextureBounds, submitFlag); + errl = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, leftTextureType, leftTextureBounds, submitFlag); + } else if( api instanceof OSVR ) { + ((OSVR)api).handleRenderBufferPresent(osvr_viewDescLeft, osvr_viewDescRight, + osvr_renderBuffer[OSVR.EYE_LEFT], osvr_renderBuffer[OSVR.EYE_LEFT]); + } + } + } else if( leftTextureType.handle == -1 || rightTextureType.handle == -1 || + leftTextureType.handle != getLeftTexId() || rightTextureType.handle != getRightTexId() ) { + leftTextureType.handle = getLeftTexId(); + if( leftTextureType.handle != -1 ) { + logger.fine("Writing Left texture to native memory at " + leftTextureType.getPointer()); + leftTextureType.write(); + if( api instanceof OSVR ) { + osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = leftTextureType.handle; + if( leftEyeDepth != null ) osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = leftEyeDepth.getImage().getId(); + osvr_renderBuffer[OSVR.EYE_LEFT].write(); + registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_LEFT]); + } + } + rightTextureType.handle = getRightTexId(); + if( rightTextureType.handle != -1 ) { + logger.fine("Writing Right texture to native memory at " + leftTextureType.getPointer()); + rightTextureType.write(); + if( api instanceof OSVR ) { + osvr_renderBuffer[OSVR.EYE_RIGHT].colorBufferName = rightTextureType.handle; + if( rightEyeDepth != null ) osvr_renderBuffer[OSVR.EYE_RIGHT].depthStencilBufferName = rightEyeDepth.getImage().getId(); + osvr_renderBuffer[OSVR.EYE_RIGHT].write(); + registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_RIGHT]); + } + } + } else { + if( api instanceof OpenVR ) { + errl = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, leftTextureType, null, + JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default); + errr = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, rightTextureType, null, + JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default); + } else if( api instanceof OSVR ) { + ((OSVR)api).handleRenderBufferPresent(osvr_viewDescFull, osvr_viewDescFull, + osvr_renderBuffer[OSVR.EYE_LEFT], osvr_renderBuffer[OSVR.EYE_RIGHT]); + } + } + + if( errl != 0 ){ + logger.severe("Submit to left compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); + logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(leftTextureType.eColorSpace)); + logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(leftTextureType.eType)); + logger.severe(" Texture handle: "+leftTextureType.handle); + + logger.severe(" Left eye texture "+leftEyeTexture.getName()+" ("+leftEyeTexture.getImage().getId()+")"); + logger.severe(" Type: "+leftEyeTexture.getType()); + logger.severe(" Size: "+leftEyeTexture.getImage().getWidth()+"x"+leftEyeTexture.getImage().getHeight()); + logger.severe(" Image depth: "+leftEyeTexture.getImage().getDepth()); + logger.severe(" Image format: "+leftEyeTexture.getImage().getFormat()); + logger.severe(" Image color space: "+leftEyeTexture.getImage().getColorSpace()); + + } + + if( errr != 0 ){ + logger.severe("Submit to right compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); + logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); + logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); + logger.severe(" Texture handle: "+rightTextureType.handle); + + logger.severe(" Right eye texture "+rightEyeTexture.getName()+" ("+rightEyeTexture.getImage().getId()+")"); + logger.severe(" Type: "+rightEyeTexture.getType()); + logger.severe(" Size: "+rightEyeTexture.getImage().getWidth()+"x"+rightEyeTexture.getImage().getHeight()); + logger.severe(" Image depth: "+rightEyeTexture.getImage().getDepth()); + logger.severe(" Image format: "+rightEyeTexture.getImage().getFormat()); + logger.severe(" Image color space: "+rightEyeTexture.getImage().getColorSpace()); + } + } + } + } + + + /** + * Initialize the VR view manager. + */ + public void initialize() { + + logger.config("Initializing VR view manager."); + + initTextureSubmitStructs(); + setupCamerasAndViews(); + setupVRScene(); + moveScreenProcessingToEyes(); + if( app.hasTraditionalGUIOverlay() ) { + + app.getVRMouseManager().init(); + + // update the pose to position the gui correctly on start + update(0f); + app.getVRGUIManager().positionGui(); + } + // if we are OSVR, our primary mirror window needs to be the same size as the render manager's output... + if( app.getVRHardware() instanceof OSVR ) { + int origWidth = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth(); + int origHeight = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getHeight(); + long window = ((LwjglWindow)application.getContext()).getWindowHandle(); + Vector2f windowSize = new Vector2f(); + ((OSVR)app.getVRHardware()).getRenderSize(windowSize); + windowSize.x = Math.max(windowSize.x * 2f, leftCamera.getWidth()); + org.lwjgl.glfw.GLFW.glfwSetWindowSize(window, (int)windowSize.x, (int)windowSize.y); + application.getContext().getSettings().setResolution((int)windowSize.x, (int)windowSize.y); + + if (application.getRenderManager() != null) { + application.getRenderManager().notifyReshape((int)windowSize.x, (int)windowSize.y); + } + + org.lwjgl.glfw.GLFW.glfwSetWindowPos(window, origWidth - (int)windowSize.x, 32); + + org.lwjgl.glfw.GLFW.glfwFocusWindow(window); + + org.lwjgl.glfw.GLFW.glfwSetCursorPos(window, origWidth / 2.0, origHeight / 2.0); + } + + logger.config("Initialized VR view manager [SUCCESS]"); + } + + /** + * Prepare the size of the given {@link Camera camera} to adapt it to the underlying rendering context. + * @param cam the {@link Camera camera} to prepare. + * @param xMult the camera width multiplier. + */ + private void prepareCameraSize(Camera cam, float xMult) { + Vector2f size = new Vector2f(); + VRAPI vrhmd = app.getVRHardware(); + + if( vrhmd == null ) { + size.x = 1280f; + size.y = 720f; + } else { + vrhmd.getRenderSize(size); + } + + if( size.x < application.getContext().getSettings().getWidth() ) { + size.x = application.getContext().getSettings().getWidth(); + } + if( size.y < application.getContext().getSettings().getHeight() ) { + size.y = application.getContext().getSettings().getHeight(); + } + + if( app.isInstanceVRRendering() ) size.x *= 2f; + + // other adjustments + size.x *= xMult; + size.x *= resMult; + size.y *= resMult; + + if( cam.getWidth() != size.x || cam.getHeight() != size.y ) cam.resize((int)size.x, (int)size.y, false); + } + + /** + * Replaces rootNode as the main cameras scene with the distortion mesh + */ + private void setupVRScene(){ + // no special scene to setup if we are doing instancing + if( app.isInstanceVRRendering() ) { + // distortion has to be done with compositor here... we want only one pass on our end! + if( application.getContext().getSettings().isSwapBuffers() ) { + setupMirrorBuffers(app.getCamera(), dualEyeTex, true); + } + return; + } + + leftEyeTexture = (Texture2D) leftViewport.getOutputFrameBuffer().getColorBuffer().getTexture(); + rightEyeTexture = (Texture2D)rightViewport.getOutputFrameBuffer().getColorBuffer().getTexture(); + leftEyeDepth = (Texture2D) leftViewport.getOutputFrameBuffer().getDepthBuffer().getTexture(); + rightEyeDepth = (Texture2D)rightViewport.getOutputFrameBuffer().getDepthBuffer().getTexture(); + + // main viewport is either going to be a distortion scene or nothing + // mirroring is handled by copying framebuffers + Iterator spatialIter = application.getViewPort().getScenes().iterator(); + while(spatialIter.hasNext()){ + application.getViewPort().detachScene(spatialIter.next()); + } + + spatialIter = application.getGuiViewPort().getScenes().iterator(); + while(spatialIter.hasNext()){ + application.getGuiViewPort().detachScene(spatialIter.next()); + } + + // only setup distortion scene if compositor isn't running (or using custom mesh distortion option) + if( app.getVRHardware().getCompositor() == null ) { + Node distortionScene = new Node(); + Material leftMat = new Material(application.getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); + leftMat.setTexture("Texture", leftEyeTexture); + Geometry leftEye = new Geometry("box", MeshUtil.setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Left, app.getVRHardware())); + leftEye.setMaterial(leftMat); + distortionScene.attachChild(leftEye); + + Material rightMat = new Material(application.getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); + rightMat.setTexture("Texture", rightEyeTexture); + Geometry rightEye = new Geometry("box", MeshUtil.setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Right, app.getVRHardware())); + rightEye.setMaterial(rightMat); + distortionScene.attachChild(rightEye); + + distortionScene.updateGeometricState(); + + application.getViewPort().attachScene(distortionScene); + + //if( useCustomDistortion ) setupFinalFullTexture(app.getViewPort().getCamera()); + } + + if( application.getContext().getSettings().isSwapBuffers() ) { + setupMirrorBuffers(app.getCamera(), leftEyeTexture, false); + } + } + + /** + * Update the VR view manager. + * This method is called by the attached {@link VRApplication VR application} and should not be called manually. + * @param tpf the time per frame. + */ + public void update(float tpf) { + + // grab the observer + Object obs = app.getObserver(); + Quaternion objRot; + Vector3f objPos; + if( obs instanceof Camera ) { + objRot = ((Camera)obs).getRotation(); + objPos = ((Camera)obs).getLocation(); + } else { + objRot = ((Spatial)obs).getWorldRotation(); + objPos = ((Spatial)obs).getWorldTranslation(); + } + // grab the hardware handle + VRAPI dev = app.getVRHardware(); + if( dev != null ) { + // update the HMD's position & orientation + dev.updatePose(); + dev.getPositionAndOrientation(hmdPos, hmdRot); + if( obs != null ) { + // update hmdPos based on obs rotation + finalRotation.set(objRot); + finalRotation.mult(hmdPos, hmdPos); + finalRotation.multLocal(hmdRot); + } + + finalizeCamera(dev.getHMDVectorPoseLeftEye(), objPos, leftCamera); + finalizeCamera(dev.getHMDVectorPoseRightEye(), objPos, rightCamera); + } else { + leftCamera.setFrame(objPos, objRot); + rightCamera.setFrame(objPos, objRot); + } + + if( app.hasTraditionalGUIOverlay() ) { + // update the mouse? + app.getVRMouseManager().update(tpf); + + // update GUI position? + if( app.getVRGUIManager().wantsReposition || app.getVRGUIManager().getPositioningMode() != VRGuiManager.POSITIONING_MODE.MANUAL ) { + app.getVRGUIManager().positionGuiNow(tpf); + app.getVRGUIManager().updateGuiQuadGeometricState(); + } + } + } + + /** + * Place the camera within the scene. + * @param eyePos the eye position. + * @param obsPosition the observer position. + * @param cam the camera to place. + */ + private void finalizeCamera(Vector3f eyePos, Vector3f obsPosition, Camera cam) { + finalRotation.mult(eyePos, finalPosition); + finalPosition.addLocal(hmdPos); + if( obsPosition != null ) finalPosition.addLocal(obsPosition); + finalPosition.y += heightAdjustment; + cam.setFrame(finalPosition, finalRotation); + } + + /** + * Handles moving filters from the main view to each eye + */ + public void moveScreenProcessingToEyes() { + if( rightViewport == null ) return; + syncScreenProcessing(application.getViewPort()); + application.getViewPort().clearProcessors(); + } + + /** + * Sets the two views to use the list of {@link SceneProcessor processors}. + * @param sourceViewport the {@link ViewPort viewport} that contains the processors to use. + */ + public void syncScreenProcessing(ViewPort sourceViewport) { + if( rightViewport == null ) return; + // setup post processing filters + if( rightPostProcessor == null ) { + rightPostProcessor = new FilterPostProcessor(application.getAssetManager()); + leftPostProcessor = new FilterPostProcessor(application.getAssetManager()); + } + // clear out all filters & processors, to start from scratch + rightPostProcessor.removeAllFilters(); + leftPostProcessor.removeAllFilters(); + leftViewport.clearProcessors(); + rightViewport.clearProcessors(); + // if we have no processors to sync, don't add the FilterPostProcessor + if( sourceViewport.getProcessors().isEmpty() ) return; + // add post processors we just made, which are empty + leftViewport.addProcessor(leftPostProcessor); + rightViewport.addProcessor(rightPostProcessor); + // go through all of the filters in the processors list + // add them to the left viewport processor & clone them to the right + for(SceneProcessor sceneProcessor : sourceViewport.getProcessors()) { + if (sceneProcessor instanceof FilterPostProcessor) { + for(Filter f : ((FilterPostProcessor)sceneProcessor).getFilterList() ) { + if( f instanceof TranslucentBucketFilter ) { + // just remove this filter, we will add it at the end manually + ((FilterPostProcessor)sceneProcessor).removeFilter(f); + } else { + leftPostProcessor.addFilter(f); + // clone to the right + Filter f2; + if(f instanceof FogFilter){ + f2 = FilterUtil.cloneFogFilter((FogFilter)f); + } else if (f instanceof CartoonSSAO ) { + f2 = new CartoonSSAO((CartoonSSAO)f); + } else if (f instanceof SSAOFilter){ + f2 = FilterUtil.cloneSSAOFilter((SSAOFilter)f); + } else if (f instanceof DirectionalLightShadowFilter){ + f2 = FilterUtil.cloneDirectionalLightShadowFilter(application.getAssetManager(), (DirectionalLightShadowFilter)f); + } else { + f2 = f; // dof, bloom, lightscattering etc. + } + rightPostProcessor.addFilter(f2); + } + } + } else if (sceneProcessor instanceof VRDirectionalLightShadowRenderer) { + // shadow processing + // TODO: make right shadow processor use same left shadow maps for performance + VRDirectionalLightShadowRenderer dlsr = (VRDirectionalLightShadowRenderer) sceneProcessor; + VRDirectionalLightShadowRenderer dlsrRight = dlsr.clone(); + dlsrRight.setLight(dlsr.getLight()); + rightViewport.getProcessors().add(0, dlsrRight); + leftViewport.getProcessors().add(0, sceneProcessor); + } + } + // make sure each has a translucent filter renderer + leftPostProcessor.addFilter(new TranslucentBucketFilter()); + rightPostProcessor.addFilter(new TranslucentBucketFilter()); + } + + private void setupCamerasAndViews() { + // get desired frustrum from original camera + Camera origCam = app.getCamera(); + float fFar = origCam.getFrustumFar(); + float fNear = origCam.getFrustumNear(); + + // if we are using OSVR get the eye info here + if( app.getVRHardware() instanceof OSVR ) { + ((OSVR)app.getVRHardware()).getEyeInfo(); + } + + // restore frustrum on distortion scene cam, if needed + if( app.isInstanceVRRendering() ) { + leftCamera = origCam; + } else if( app.compositorAllowed() == false ) { + origCam.setFrustumFar(100f); + origCam.setFrustumNear(1f); + leftCamera = origCam.clone(); + prepareCameraSize(origCam, 2f); + } else { + leftCamera = origCam.clone(); + } + + leftCamera.setFrustumPerspective(app.getDefaultFOV(), app.getDefaultAspect(), fNear, fFar); + + prepareCameraSize(leftCamera, 1f); + if( app.getVRHardware() != null ) leftCamera.setProjectionMatrix(app.getVRHardware().getHMDMatrixProjectionLeftEye(leftCamera)); + //org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_SRGB); + + if( !app.isInstanceVRRendering()) { + leftViewport = setupViewBuffers(leftCamera, LEFT_VIEW_NAME); + rightCamera = leftCamera.clone(); + if( app.getVRHardware() != null ){ + rightCamera.setProjectionMatrix(app.getVRHardware().getHMDMatrixProjectionRightEye(rightCamera)); + } + rightViewport = setupViewBuffers(rightCamera, RIGHT_VIEW_NAME); + } else { + + System.err.println("[VRViewManager] THIS CODE NEED CHANGES !!!"); + leftViewport = application.getViewPort(); + //leftViewport.attachScene(app.getRootNode()); + rightCamera = leftCamera.clone(); + if( app.getVRHardware() != null ){ + rightCamera.setProjectionMatrix(app.getVRHardware().getHMDMatrixProjectionRightEye(rightCamera)); + } + + org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_CLIP_DISTANCE0); + + //FIXME: [jme-vr] Fix with JMonkey next release + //RenderManager._VRInstancing_RightCamProjection = camRight.getViewProjectionMatrix(); + setupFinalFullTexture(application.getViewPort().getCamera()); + } + + // setup gui + app.getVRGUIManager().setupGui(leftCamera, rightCamera, leftViewport, rightViewport); + + if( app.getVRHardware() != null ) { + // call these to cache the results internally + app.getVRHardware().getHMDMatrixPoseLeftEye(); + app.getVRHardware().getHMDMatrixPoseRightEye(); + } + + } + + private ViewPort setupMirrorBuffers(Camera cam, Texture tex, boolean expand) { + Camera clonecam = cam.clone(); + ViewPort viewPort = application.getRenderManager().createPostView("MirrorView", clonecam); + clonecam.setParallelProjection(true); + viewPort.setClearFlags(true, true, true); + viewPort.setBackgroundColor(ColorRGBA.Black); + Picture pic = new Picture("fullscene"); + pic.setLocalTranslation(-0.75f, -0.5f, 0f); + if( expand ) { + pic.setLocalScale(3f, 1f, 1f); + } else { + pic.setLocalScale(1.5f, 1f, 1f); + } + pic.setQueueBucket(Bucket.Opaque); + pic.setTexture(application.getAssetManager(), (Texture2D)tex, false); + viewPort.attachScene(pic); + viewPort.setOutputFrameBuffer(null); + + pic.updateGeometricState(); + + return viewPort; + } + + private void setupFinalFullTexture(Camera cam) { + // create offscreen framebuffer + FrameBuffer out = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); + //offBuffer.setSrgb(true); + + //setup framebuffer's texture + dualEyeTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); + dualEyeTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + dualEyeTex.setMagFilter(Texture.MagFilter.Bilinear); + + logger.config("Dual eye texture "+dualEyeTex.getName()+" ("+dualEyeTex.getImage().getId()+")"); + logger.config(" Type: "+dualEyeTex.getType()); + logger.config(" Size: "+dualEyeTex.getImage().getWidth()+"x"+dualEyeTex.getImage().getHeight()); + logger.config(" Image depth: "+dualEyeTex.getImage().getDepth()); + logger.config(" Image format: "+dualEyeTex.getImage().getFormat()); + logger.config(" Image color space: "+dualEyeTex.getImage().getColorSpace()); + + //setup framebuffer to use texture + out.setDepthBuffer(Image.Format.Depth); + out.setColorTexture(dualEyeTex); + + ViewPort viewPort = application.getViewPort(); + viewPort.setClearFlags(true, true, true); + viewPort.setBackgroundColor(ColorRGBA.Black); + viewPort.setOutputFrameBuffer(out); + + } + + private ViewPort setupViewBuffers(Camera cam, String viewName){ + // create offscreen framebuffer + FrameBuffer offBufferLeft = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); + //offBufferLeft.setSrgb(true); + + //setup framebuffer's texture + Texture2D offTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); + offTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); + offTex.setMagFilter(Texture.MagFilter.Bilinear); + + //setup framebuffer to use texture + offBufferLeft.setDepthBuffer(Image.Format.Depth); + offBufferLeft.setColorTexture(offTex); + + ViewPort viewPort = application.getRenderManager().createPreView(viewName, cam); + viewPort.setClearFlags(true, true, true); + viewPort.setBackgroundColor(ColorRGBA.Black); + + Iterator spatialIter = application.getViewPort().getScenes().iterator(); + while(spatialIter.hasNext()){ + viewPort.attachScene(spatialIter.next()); + } + + //set viewport to render to offscreen framebuffer + viewPort.setOutputFrameBuffer(offBufferLeft); + return viewPort; + } +}