package com.jme3.app ;
import com.jme3.app.AppTask ;
import com.jme3.app.Application ;
import com.jme3.app.LegacyApplication ;
import com.jme3.app.LostFocusBehavior ;
import com.jme3.app.ResetStatsState ;
import com.jme3.app.SimpleApplication ;
import com.jme3.app.state.AppState ;
import com.jme3.app.state.AppStateManager ;
import com.jme3.asset.AssetManager ;
import com.jme3.audio.AudioContext ;
import com.jme3.audio.AudioRenderer ;
import com.jme3.audio.Listener ;
import com.jme3.input.InputManager ;
import com.jme3.input.JoyInput ;
import com.jme3.input.KeyInput ;
import com.jme3.input.MouseInput ;
import com.jme3.input.TouchInput ;
import com.jme3.input.controls.KeyTrigger ;
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.profile.AppProfiler ;
import com.jme3.renderer.Camera ;
import com.jme3.renderer.RenderManager ;
import com.jme3.renderer.Renderer ;
import com.jme3.renderer.ViewPort ;
import com.jme3.renderer.queue.RenderQueue.Bucket ;
import com.jme3.scene.Node ;
import com.jme3.scene.Spatial ;
import com.jme3.scene.Spatial.CullHint ;
import com.jme3.system.AppSettings ;
import com.jme3.system.JmeContext ;
import com.jme3.system.JmeContext.Type ;
import com.jme3.system.jopenvr.JOpenVRLibrary ;
import com.jme3.system.JmeSystem ;
import com.jme3.system.NanoTimer ;
import com.jme3.system.SystemListener ;
import com.jme3.system.Timer ;
import com.jme3.system.lwjgl.LwjglDisplayVR ;
import com.jme3.system.lwjgl.LwjglOffscreenBufferVR ;
import java.awt.GraphicsDevice ;
import java.awt.GraphicsEnvironment ;
import java.awt.HeadlessException ;
import java.io.BufferedReader ;
import java.io.File ;
import java.io.FileReader ;
import java.net.MalformedURLException ;
import java.net.URL ;
import java.util.Locale ;
import java.util.concurrent.Callable ;
import java.util.concurrent.ConcurrentLinkedQueue ;
import java.util.concurrent.Future ;
import java.util.logging.Level ;
import java.util.logging.Logger ;
import jmevr.util.VRViewManager ;
import jmevr.util.VRGuiManager ;
import jmevr.util.VRGuiManager.POSITIONING_MODE ;
import jmevr.util.VRMouseManager ;
import org.lwjgl.system.Platform ;
/ * *
* A JMonkey application dedicated to Virtual Reality . An application that use VR devices ( HTC vive , . . . ) has to extends this one . < br >
* < p >
* < b > This class is no more functional and is deprecated . Please use { @link VRAppState VRAppState } instead . < / b >
* @author reden - phr00t - https : //github.com/phr00t
* @author Julien Seinturier - ( c ) 2016 - JOrigin project - < a href = "http://www.jorigin.org" > http : / www . jorigin . org < / a >
* @deprecated use { @link VRAppState VRAppState } instead .
* /
public abstract class VRApplication implements Application , SystemListener {
private static final Logger logger = Logger . getLogger ( LegacyApplication . class . getName ( ) ) ;
/ * *
* The default FOV .
* /
public float DEFAULT_FOV = 108f ;
/ * *
* The default aspect ratio .
* /
public float DEFAULT_ASPECT = 1f ;
/ * *
* Is the application is based on OSVR ( default is < code > false < / code > ) .
* /
public boolean CONSTRUCT_WITH_OSVR = false ;
/ * *
* Is the application has not to start within VR mode ( default is < code > false < / code > ) .
* /
public boolean DISABLE_VR = false ;
/ * *
* VR application configuration parameters .
* @author reden - phr00t - https : //github.com/phr00t
* @author Julien Seinturier - ( c ) 2016 - JOrigin project - < a href = "http://www.jorigin.org" > http : / www . jorigin . org < / a >
*
* /
public static enum PreconfigParameter {
/ * *
* Is the SteamVR compositor is used ( kinda needed at the moment )
* /
USE_VR_COMPOSITOR ,
/ * *
* Render two eyes , regardless of VR API detection .
* /
FORCE_VR_MODE ,
/ * *
* Invert the eyes .
* /
FLIP_EYES ,
/ * *
* Show GUI even if it is behind objects .
* /
SET_GUI_OVERDRAW ,
/ * *
*
* /
SET_GUI_CURVED_SURFACE ,
/ * *
* Display a mirror rendering on the screen . Runs faster when set to < code > false < / code > .
* /
ENABLE_MIRROR_WINDOW ,
/ * *
*
* /
PREFER_OPENGL3 ,
/ * *
* Disable VR rendering , regardless VR API and devices are presents .
* /
DISABLE_VR ,
/ * *
*
* /
SEATED_EXPERIENCE ,
/ * *
* Remove GUI node from the application .
* /
NO_GUI ,
/ * *
* Faster VR rendering , requires some vertex shader changes ( see Common / MatDefs / VR / Unshaded . j3md )
* /
INSTANCE_VR_RENDERING ,
/ * *
*
* /
FORCE_DISABLE_MSAA
}
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 ;
private boolean VRSupportedOS ;
private boolean forceVR ;
private boolean disableSwapBuffers = true ;
private boolean tryOpenGL3 = true ;
private boolean seated ;
private boolean nogui ;
private boolean instanceVR ;
private boolean forceDisableMSAA ;
// things taken from LegacyApplication
private AppStateManager stateManager ;
private Camera cam ;
private AppSettings settings ;
private JmeContext context ;
private float speed = 1f ;
private AudioRenderer audioRenderer ;
private LostFocusBehavior lostFocusBehavior = LostFocusBehavior . ThrottleOnLostFocus ;
private final ConcurrentLinkedQueue < AppTask < ? > > taskQueue = new ConcurrentLinkedQueue < AppTask < ? > > ( ) ;
private Timer timer = new NanoTimer ( ) ;
private boolean paused = false , inputEnabled = true ;
private InputManager inputManager ;
private RenderManager renderManager ;
private ViewPort viewPort ;
private ViewPort guiViewPort ;
private AssetManager assetManager ;
private Renderer renderer ;
private Listener listener ;
private MouseInput mouseInput ;
private KeyInput keyInput ;
private JoyInput joyInput ;
private TouchInput touchInput ;
protected Node guiNode , rootNode ;
private float fFar = 1000f , fNear = 1f ;
private int xWin = 1280 , yWin = 720 ;
private float resMult = 1f ;
private boolean useCompositor = true , compositorOS ;
private final String RESET_HMD = "ResetHMD" ;
/ * *
* Create a new VR application and attach the given { @link AppState app states } . < br >
* The application scene is made of a { @link # getRootNode ( ) root node } that holds the scene spatials
* and a { @link # getGuiNode ( ) GUI node } that is the root of the Graphical user interface .
* @param initialStates the { @link AppState app states } to attach to the application .
* /
public VRApplication ( AppState . . . initialStates ) {
this ( ) ;
if ( initialStates ! = null ) {
for ( AppState a : initialStates ) {
if ( a ! = null ) {
stateManager . attach ( a ) ;
}
}
}
}
/ * *
* Create a new VR application . < br >
* The application scene is made of a { @link # getRootNode ( ) root node } that holds the scene spatials
* and a { @link # getGuiNode ( ) GUI node } that is the root of the Graphical user interface .
* /
public VRApplication ( ) {
super ( ) ;
rootNode = new Node ( "root" ) ;
guiNode = new Node ( "guiNode" ) ;
guiNode . setQueueBucket ( Bucket . Gui ) ;
guiNode . setCullHint ( CullHint . Never ) ;
dummyCam = new Camera ( ) ;
initStateManager ( ) ;
// Create the GUI manager.
guiManager = new VRGuiManager ( ) ;
// Create a new view manager.
viewmanager = new VRViewManager ( ) ;
// Create a new mouse manager.
mouseManager = new VRMouseManager ( ) ;
// 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 ) {
logger . warning ( "Non-supported OS: " + OS + ", architecture: " + System . getProperty ( "sun.arch.data.model" ) ) ;
} else if ( DISABLE_VR ) {
logger . warning ( "VR disabled via code." ) ;
} else if ( VRSupportedOS & & DISABLE_VR = = false ) {
if ( CONSTRUCT_WITH_OSVR ) {
//FIXME: WARNING !!
VRhardware = new OSVR ( null ) ;
logger . config ( "Creating OSVR wrapper [SUCCESS]" ) ;
} else {
//FIXME: WARNING !!
VRhardware = new OpenVR ( null ) ;
logger . config ( "Creating OpenVR wrapper [SUCCESS]" ) ;
}
if ( VRhardware . initialize ( ) ) {
setPauseOnLostFocus ( false ) ;
}
}
}
/ * *
* 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 ;
}
/ * *
* 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 SteamVR compositor is active .
* @return < code > true < / code > if the SteamVR compositor is active and < code > false < / code > otherwise .
* /
public boolean compositorAllowed ( ) {
return useCompositor & & compositorOS ;
}
/ * *
* Get if the system currently support VR .
* @return < code > true < / code > if the system currently support VR and < code > false < / Code > otherwise .
* /
public boolean isOSVRSupported ( ) {
return VRSupportedOS ;
}
/ * *
* Simple update of the application , this method should contains { @link # getRootNode ( ) root node } updates .
* This method is called by the { @link # update ( ) update ( ) } method and should not be called manually .
* @param tpf the application time .
* /
public void simpleUpdate ( float tpf ) { }
/ * *
* Rendering callback of the application . This method is called by the { @link # update ( ) update ( ) } method and should not be called manually .
* @param renderManager the { @link RenderManager render manager } .
* /
public void simpleRender ( RenderManager renderManager ) {
PreNormalCaching . resetCache ( isInVR ( ) ) ;
}
/ *
we do NOT want to get & modify the distortion scene camera , so
return the left viewport camera instead if we are in VR mode
* /
@Override
public Camera getCamera ( ) {
if ( isInVR ( ) & & viewmanager ! = null & & viewmanager . getLeftCamera ( ) ! = null ) {
return dummyCam ;
}
return cam ;
}
/ * *
* Get the application internal camera .
* @return the application internal camera .
* @see # getCamera ( )
* /
public Camera getBaseCamera ( ) {
return cam ;
}
@Override
public JmeContext getContext ( ) {
return context ;
}
@Override
public AssetManager getAssetManager ( ) {
return assetManager ;
}
@Override
public InputManager getInputManager ( ) {
return inputManager ;
}
@Override
public AppStateManager getStateManager ( ) {
return stateManager ;
}
@Override
public RenderManager getRenderManager ( ) {
return renderManager ;
}
@Override
public Renderer getRenderer ( ) {
return renderer ;
}
@Override
public AudioRenderer getAudioRenderer ( ) {
return audioRenderer ;
}
@Override
public Listener getListener ( ) {
return listener ;
}
@Override
public Timer getTimer ( ) {
return timer ;
}
/ * *
* Handle the error given in parameters by creating a log entry and a dialog window . Internal use only .
* /
public void handleError ( String errMsg , Throwable t ) {
// Print error to log.
logger . log ( Level . SEVERE , errMsg , t ) ;
// Display error message on screen if not in headless mode
if ( context . getType ( ) ! = JmeContext . Type . Headless ) {
if ( t ! = null ) {
JmeSystem . showErrorDialog ( errMsg + "\n" + t . getClass ( ) . getSimpleName ( ) +
( t . getMessage ( ) ! = null ? ": " + t . getMessage ( ) : "" ) ) ;
} else {
JmeSystem . showErrorDialog ( errMsg ) ;
}
}
stop ( ) ; // stop the application
}
/ * *
* Force the focus gain for the application . Internal use only .
* /
public void gainFocus ( ) {
if ( lostFocusBehavior ! = LostFocusBehavior . Disabled ) {
if ( lostFocusBehavior = = LostFocusBehavior . PauseOnLostFocus ) {
paused = false ;
}
context . setAutoFlushFrames ( true ) ;
if ( inputManager ! = null ) {
inputManager . reset ( ) ;
}
}
}
/ * *
* Force the focus lost for the application . Internal use only .
* /
public void loseFocus ( ) {
if ( lostFocusBehavior ! = LostFocusBehavior . Disabled ) {
if ( lostFocusBehavior = = LostFocusBehavior . PauseOnLostFocus ) {
paused = true ;
}
context . setAutoFlushFrames ( false ) ;
}
}
/ * *
* Reshape the display window . Internal use only .
* /
public void reshape ( int w , int h ) {
if ( renderManager ! = null ) {
renderManager . notifyReshape ( w , h ) ;
}
}
/ * *
* Request the application to close . Internal use only .
* /
public void requestClose ( boolean esc ) {
context . destroy ( false ) ;
}
/ * *
* Set the { @link AppSettings display settings } to define the display created .
* < p >
* Examples of display parameters include display frame { @link AppSettings # getWidth ( ) width } and { @link AppSettings # getHeight ( ) height } ,
* pixel { @link AppSettings # getBitsPerPixel ( ) color bit depth } , { @link AppSettings # getDepthBits ( ) z - buffer bits } , { @link AppSettings # getSamples ( ) anti - aliasing samples } , { @link AppSettings # getFrequency ( ) update frequency } , . . .
* < br > < br > If this method is called while the application is already running , then
* { @link # restart ( ) } must be called to apply the settings to the display .
*
* @param settings The settings to set .
* /
public void setSettings ( AppSettings settings ) {
this . settings = settings ;
if ( context ! = null & & settings . useInput ( ) ! = inputEnabled ) {
// may need to create or destroy input based
// on settings change
inputEnabled = ! inputEnabled ;
if ( inputEnabled ) {
initInput ( ) ;
} else {
destroyInput ( ) ;
}
} else {
inputEnabled = settings . useInput ( ) ;
}
}
/ * *
* Sets the { @link Timer } implementation that will be used for calculating
* frame times . < br > < br >
* By default , Application will use the Timer as returned by the current { @link JmeContext } implementation .
* @param timer the timer to use .
* /
public void setTimer ( Timer timer ) {
this . timer = timer ;
if ( timer ! = null ) {
timer . reset ( ) ;
}
if ( renderManager ! = null ) {
renderManager . setTimer ( timer ) ;
}
}
/ * *
* Determine the application ' s behavior when unfocused .
* @return The lost focus behavior of the application .
* /
public LostFocusBehavior getLostFocusBehavior ( ) {
return lostFocusBehavior ;
}
/ * *
* Change the application ' s behavior when unfocused . By default , the application will
* { @link LostFocusBehavior # ThrottleOnLostFocus throttle the update loop }
* so as to not take 100 % CPU usage when it is not in focus , e . g .
* alt - tabbed , minimized , or obstructed by another window .
*
* @param lostFocusBehavior The new { @link LostFocusBehavior lost focus behavior } to use .
* /
public void setLostFocusBehavior ( LostFocusBehavior lostFocusBehavior ) {
this . lostFocusBehavior = lostFocusBehavior ;
}
/ * *
* Get if the application has to pause then it lost the focus .
* @return < code > true < / code > if pause on lost focus is enabled , < code > false < / code > otherwise .
* @see # getLostFocusBehavior ( )
* /
public boolean isPauseOnLostFocus ( ) {
return getLostFocusBehavior ( ) = = LostFocusBehavior . PauseOnLostFocus ;
}
/ * *
* Enable or disable pause on lost focus .
* < p >
* By default , pause on lost focus is enabled .
* If enabled , the application will stop updating
* when it loses focus or becomes inactive ( e . g . alt - tab ) .
* For online or real - time applications , this might not be preferable ,
* so this feature should be set to disabled . For other applications ,
* it is best to keep it on so that CPU usage is not used when
* not necessary .
*
* @param pauseOnLostFocus < code > true < / code > to enable pause on lost focus , < code > false < / code >
* otherwise .
*
* @see # setLostFocusBehavior ( com . jme3 . app . LostFocusBehavior )
* /
public void setPauseOnLostFocus ( boolean pauseOnLostFocus ) {
if ( pauseOnLostFocus ) {
setLostFocusBehavior ( LostFocusBehavior . PauseOnLostFocus ) ;
} else {
setLostFocusBehavior ( LostFocusBehavior . Disabled ) ;
}
}
@Override
public void start ( ) {
logger . config ( "Starting application..." ) ;
// set some default settings in-case
// settings dialog is not shown
boolean loadSettings = false ;
if ( settings = = null ) {
setSettings ( new AppSettings ( true ) ) ;
loadSettings = true ;
}
GraphicsDevice defDev = null ;
try {
GraphicsEnvironment ge = GraphicsEnvironment . getLocalGraphicsEnvironment ( ) ;
defDev = ge . getDefaultScreenDevice ( ) ;
} catch ( Throwable e1 ) {
logger . log ( Level . SEVERE , "Cannot access default screen device: " + e1 . getMessage ( ) , e1 ) ;
}
if ( isInVR ( ) & & ! compositorAllowed ( ) ) {
logger . warning ( "VR Composition is not allowed." ) ;
// "easy extended" mode
// TO-DO: JFrame was removed in LWJGL 3, need to use new GLFW library to pick "monitor" display of VR 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
setSettings ( settings ) ;
//VRdev.setFullScreenWindow(VRwindow);
// make sure we are in the right display mode
if ( VRdev . getDisplayMode ( ) . equals ( useDM ) = = false ) {
VRdev . setDisplayMode ( useDM ) ;
}
// make a blank cursor to hide it
//BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
//Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0), "blank cursor");
//VRwindow.setCursor(blankCursor);
//jmeCanvas.getCanvas().setCursor(blankCursor);
//VRwindow.pack();
//VRwindow.setVisible(true);
//startCanvas();
logger . config ( "Starting application [SUCCESS]" ) ;
return ;
} catch ( Exception e ) {
logger . log ( Level . SEVERE , "Error during application start: " + e . getMessage ( ) , e ) ;
}
}
}
if ( ! isInVR ( ) ) {
logger . config ( "VR mode disabled." ) ;
// not in VR, show settings dialog
if ( Platform . get ( ) ! = Platform . MACOSX ) {
if ( ! JmeSystem . showSettingsDialog ( settings , loadSettings ) ) {
logger . config ( "Starting application [SUCCESS]" ) ;
return ;
}
} else {
// 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 {
logger . config ( "VR mode enabled." ) ;
// use basic mirroring window, skip settings window
settings . setWidth ( xWin ) ;
settings . setHeight ( yWin ) ;
settings . setBitsPerPixel ( 24 ) ;
settings . setFrameRate ( 0 ) ; // never sleep in main loop
settings . setFrequency ( VRhardware . getDisplayFrequency ( ) ) ;
settings . setFullscreen ( false ) ;
settings . setVSync ( false ) ; // stop vsyncing on primary monitor!
settings . setSwapBuffers ( ! disableSwapBuffers | | VRhardware instanceof OSVR ) ;
settings . setTitle ( "Put Headset On Now: " + settings . getTitle ( ) ) ;
settings . setResizable ( true ) ;
}
if ( forceDisableMSAA ) {
logger . config ( "Disabling multisampling." ) ;
// disable multisampling, which is more likely to break things than be useful
settings . setSamples ( 1 ) ;
}
// set opengl mode
if ( tryOpenGL3 ) {
logger . config ( "Using LWJGL OpenGL 3 renderer." ) ;
settings . setRenderer ( AppSettings . LWJGL_OPENGL3 ) ;
} else {
logger . config ( "Using LWJGL OpenGL 2 renderer." ) ;
settings . setRenderer ( AppSettings . LWJGL_OPENGL2 ) ;
}
setSettings ( settings ) ;
start ( JmeContext . Type . Display , false ) ;
// 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 ) ;
}
/ * *
* Starts the application in { @link com . jme3 . system . JmeContext . Type # Display display } mode .
* @param waitFor if < code > true < / code > , the method will wait until the application is started .
* @see # start ( com . jme3 . system . JmeContext . Type , boolean )
* /
public void start ( boolean waitFor ) {
start ( JmeContext . Type . Display , waitFor ) ;
}
/ * *
* Starts the application .
* Creating a rendering context and executing the main loop in a separate thread .
* @param contextType the { @link com . jme3 . system . JmeContext . Type type } of the context to create .
* @param waitFor if < code > true < / code > , the method will wait until the application is started .
* @throws IllegalArgumentException if the context type is not supported .
* /
public void start ( JmeContext . Type contextType , boolean waitFor ) {
if ( context ! = null & & context . isCreated ( ) ) {
logger . warning ( "start() called when application already created!" ) ;
return ;
}
if ( settings = = null ) {
settings = new AppSettings ( true ) ;
}
logger . log ( Level . FINE , "Starting application: {0}" , getClass ( ) . getName ( ) ) ;
// Create VR decicated context
if ( contextType = = Type . Display ) {
context = new LwjglDisplayVR ( ) ;
context . setSettings ( settings ) ;
} else if ( contextType = = Type . OffscreenSurface ) {
context = new LwjglOffscreenBufferVR ( ) ;
context . setSettings ( settings ) ;
} else {
logger . severe ( "Unsupported context type \"" + contextType + "\". Supported are \"Display\" and \"OffscreenSurface\"" ) ;
throw new IllegalArgumentException ( "Unsupported context type \"" + contextType + "\". Supported are \"Display\" and \"OffscreenSurface\"" ) ;
}
context . setSystemListener ( this ) ;
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
* @param parm the parameter to set .
* @param value the value of the parameter .
* /
public void preconfigureVRApp ( PreconfigParameter parm , boolean value ) {
switch ( parm ) {
case SET_GUI_OVERDRAW :
guiManager . _enableGuiOverdraw ( value ) ;
break ;
case SET_GUI_CURVED_SURFACE :
guiManager . _enableCurvedSuface ( value ) ;
break ;
case FORCE_VR_MODE :
forceVR = value ;
break ;
//case USE_CUSTOM_DISTORTION: //deprecated, always using a render manager
// VRViewManager._setCustomDistortion(value);
// break;
case USE_VR_COMPOSITOR :
useCompositor = value ;
if ( value = = false ) disableSwapBuffers = false ;
break ;
case FLIP_EYES :
if ( VRhardware = = null ) return ;
VRhardware . _setFlipEyes ( value ) ;
break ;
case INSTANCE_VR_RENDERING :
instanceVR = value ;
break ;
case ENABLE_MIRROR_WINDOW :
if ( useCompositor = = false ) {
disableSwapBuffers = false ;
} else disableSwapBuffers = ! value ;
break ;
case PREFER_OPENGL3 :
tryOpenGL3 = value ;
break ;
case DISABLE_VR :
DISABLE_VR = value ;
break ;
case NO_GUI :
nogui = value ;
break ;
case SEATED_EXPERIENCE :
seated = value ;
break ;
case FORCE_DISABLE_MSAA :
forceDisableMSAA = value ;
break ;
}
}
/ * *
* Can be used to change seated experience during runtime .
* @param isSeated < code > true < / code > if designed for sitting , < code > false < / code > 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 < code > true < / code > if the application is configured as a seated experience and < code > false < / code > 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 < a href = "https://en.wikipedia.org/wiki/Geometry_instancing" > Geometry instancing < / a > ) .
* @return < code > true < / code > if the rendering is instanced and < code > false < / code > otherwise .
* /
public boolean isInstanceVRRendering ( ) {
return instanceVR & & isInVR ( ) ;
}
/ * *
* Check if the VR mode is enabled .
* @return < code > true < / code > if the VR mode is enabled and < code > false < / code > otherwise .
* /
public boolean isInVR ( ) {
return DISABLE_VR = = false & & ( forceVR | | VRSupportedOS & & VRhardware ! = null & & VRhardware . isInitialized ( ) ) ;
}
/ * *
* Get the GUI node from the application .
* @return the GUI node from the application .
* @see # setGuiNode ( Node )
* /
public Node getGuiNode ( ) {
return guiNode ;
}
/ * *
* Set the GUI node that is displayed within the GUI viewport .
* Calling this method involve clearing all the scenes previously attached to the gui viewport .
* @param node the GUI node to attach .
* @see # getGuiNode ( )
* /
public void setGuiNode ( Node node ) {
if ( node ! = null ) {
if ( guiViewPort ! = null ) {
enqueue ( new Callable < Object > ( ) {
@Override
public Object call ( ) throws Exception {
guiViewPort . clearScenes ( ) ;
guiViewPort . attachScene ( node ) ;
guiNode = node ;
return null ;
}
} ) ;
} else {
throw new IllegalArgumentException ( "GUI view port is not initialized." ) ;
}
}
}
/ * *
* Get the root node of the application .
* @return the root node of the application .
* /
public Node getRootNode ( ) {
return rootNode ;
}
/ * *
* Check if the application has a GUI overlay attached .
* @return < code > true < / code > if the application has a GUI overlay attached and < code > false < / code > 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 ;
}
/ *
where is the headset pointing , after all rotations are combined ?
depends on observer rotation , if any
* /
private static Quaternion tempq = new Quaternion ( ) ;
/ * *
* 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 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 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 ) {
getViewPort ( ) . setBackgroundColor ( clr ) ;
} else if ( viewmanager . getLeftViewport ( ) ! = null ) {
viewmanager . getLeftViewport ( ) . setBackgroundColor ( clr ) ;
if ( viewmanager . getRightViewport ( ) ! = null ) viewmanager . getRightViewport ( ) . setBackgroundColor ( clr ) ;
}
}
/ * *
* Runs tasks enqueued via { @link # enqueue ( Callable ) }
* /
protected void runQueuedTasks ( ) {
AppTask < ? > task ;
while ( ( task = taskQueue . poll ( ) ) ! = null ) {
if ( ! task . isCancelled ( ) ) {
task . invoke ( ) ;
}
}
}
@Override
public void update ( ) {
// Make sure the audio renderer is available to callables
AudioContext . setAudioRenderer ( audioRenderer ) ;
runQueuedTasks ( ) ;
if ( speed ! = 0 & & ! paused ) {
timer . update ( ) ;
if ( inputEnabled ) {
inputManager . update ( timer . getTimePerFrame ( ) ) ;
}
if ( audioRenderer ! = null ) {
audioRenderer . update ( timer . getTimePerFrame ( ) ) ;
}
}
if ( speed = = 0 | | paused ) {
try {
Thread . sleep ( 50 ) ; // throttle the CPU when paused
} catch ( InterruptedException ex ) {
Logger . getLogger ( SimpleApplication . class . getName ( ) ) . log ( Level . SEVERE , null , ex ) ;
}
return ;
}
float tpf = timer . getTimePerFrame ( ) * speed ;
// update states
stateManager . update ( tpf ) ;
// simple update and root node
simpleUpdate ( tpf ) ;
// render states
stateManager . render ( renderManager ) ;
// 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.
rootNode . updateLogicalState ( tpf ) ;
guiNode . updateLogicalState ( tpf ) ;
rootNode . 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
guiNode . updateGeometricState ( ) ;
}
renderManager . render ( tpf , context . isRenderable ( ) ) ;
simpleRender ( renderManager ) ;
stateManager . postRender ( ) ;
// update compositor?
if ( viewmanager ! = null ) {
viewmanager . sendTextures ( ) ;
}
}
private void initAssetManager ( ) {
URL assetCfgUrl = null ;
if ( settings ! = null ) {
String assetCfg = settings . getString ( "AssetConfigURL" ) ;
if ( assetCfg ! = null ) {
try {
assetCfgUrl = new URL ( assetCfg ) ;
} catch ( MalformedURLException ex ) {
}
if ( assetCfgUrl = = null ) {
assetCfgUrl = LegacyApplication . class . getClassLoader ( ) . getResource ( assetCfg ) ;
if ( assetCfgUrl = = null ) {
logger . log ( Level . SEVERE , "Unable to access AssetConfigURL in asset config:{0}" , assetCfg ) ;
return ;
}
}
}
}
if ( assetCfgUrl = = null ) {
assetCfgUrl = JmeSystem . getPlatformAssetConfigURL ( ) ;
}
if ( assetManager = = null ) {
assetManager = JmeSystem . newAssetManager ( assetCfgUrl ) ;
logger . config ( "Created asset manager from " + assetCfgUrl ) ;
}
}
private void initDisplay ( ) {
// aquire important objects
// from the context
settings = context . getSettings ( ) ;
// Only reset the timer if a user has not already provided one
if ( timer = = null ) {
timer = context . getTimer ( ) ;
}
renderer = context . getRenderer ( ) ;
}
private void initAudio ( ) {
if ( settings . getAudioRenderer ( ) ! = null & & context . getType ( ) ! = JmeContext . Type . Headless ) {
audioRenderer = JmeSystem . newAudioRenderer ( settings ) ;
audioRenderer . initialize ( ) ;
AudioContext . setAudioRenderer ( audioRenderer ) ;
listener = new Listener ( ) ;
audioRenderer . setListener ( listener ) ;
}
}
/ * *
* Creates the camera to use for rendering . Default values are perspective
* projection with 45 ° field of view , with near and far values 1 and 1000
* units respectively .
* /
private void initCamera ( ) {
cam = new Camera ( settings . getWidth ( ) , settings . getHeight ( ) ) ;
cam . setFrustumPerspective ( 45f , ( float ) cam . getWidth ( ) / cam . getHeight ( ) , 1f , 1000f ) ;
cam . setLocation ( new Vector3f ( 0f , 0f , 10f ) ) ;
cam . lookAt ( new Vector3f ( 0f , 0f , 0f ) , Vector3f . UNIT_Y ) ;
renderManager = new RenderManager ( renderer ) ;
//Remy - 09/14/2010 setted the timer in the renderManager
renderManager . setTimer ( timer ) ;
viewPort = renderManager . createMainView ( "Default" , cam ) ;
viewPort . setClearFlags ( true , true , true ) ;
// Create a new cam for the gui
Camera guiCam = new Camera ( settings . getWidth ( ) , settings . getHeight ( ) ) ;
guiViewPort = renderManager . createPostView ( "Gui Default" , guiCam ) ;
guiViewPort . setClearFlags ( false , false , false ) ;
}
/ * *
* Initializes mouse and keyboard input . Also
* initializes joystick input if joysticks are enabled in the
* AppSettings .
* /
private void initInput ( ) {
mouseInput = context . getMouseInput ( ) ;
if ( mouseInput ! = null )
mouseInput . initialize ( ) ;
keyInput = context . getKeyInput ( ) ;
if ( keyInput ! = null )
keyInput . initialize ( ) ;
touchInput = context . getTouchInput ( ) ;
if ( touchInput ! = null )
touchInput . initialize ( ) ;
if ( ! settings . getBoolean ( "DisableJoysticks" ) ) {
joyInput = context . getJoyInput ( ) ;
if ( joyInput ! = null )
joyInput . initialize ( ) ;
}
inputManager = new InputManager ( mouseInput , keyInput , joyInput , touchInput ) ;
}
private void initStateManager ( ) {
stateManager = new AppStateManager ( this ) ;
// Always register a ResetStatsState to make sure
// that the stats are cleared every frame
stateManager . attach ( new ResetStatsState ( ) ) ;
}
/ * *
* Do not call manually .
* Callback from ContextListener .
* < p >
* Initializes the < code > Application < / code > , by creating a display and
* default camera . If display settings are not specified , a default
* 640x480 display is created . Default values are used for the camera ;
* perspective projection with 45 ° field of view , with near
* and far values 1 and 1000 units respectively .
* /
private void initialize_internal ( ) {
if ( assetManager = = null ) {
initAssetManager ( ) ;
}
initDisplay ( ) ;
initCamera ( ) ;
if ( inputEnabled ) {
initInput ( ) ;
}
initAudio ( ) ;
// update timer so that the next delta is not too large
// timer.update();
timer . reset ( ) ;
// user code here..
}
@Override
public void initialize ( ) {
logger . config ( "Initialize VR application..." ) ;
initialize_internal ( ) ;
cam . setFrustumFar ( fFar ) ;
cam . setFrustumNear ( fNear ) ;
dummyCam = cam . clone ( ) ;
if ( isInVR ( ) ) {
logger . config ( "VR mode enabled." ) ;
if ( VRhardware ! = null ) {
VRhardware . initVRCompositor ( compositorAllowed ( ) ) ;
} else {
logger . warning ( "No VR system found." ) ;
}
//FIXME: WARNING !!
viewmanager = new VRViewManager ( ) ;
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 ( ) ;
}
simpleInitApp ( ) ;
// any filters created, move them now
if ( viewmanager ! = null ) {
viewmanager . moveScreenProcessingToEyes ( ) ;
// print out camera information
if ( isInVR ( ) ) {
logger . info ( "VR Initialization Information" ) ;
if ( viewmanager . getLeftCamera ( ) ! = null ) {
logger . info ( "camLeft: " + viewmanager . getLeftCamera ( ) . toString ( ) ) ;
}
if ( viewmanager . getRightCamera ( ) ! = null ) {
logger . info ( "camRight: " + viewmanager . getRightCamera ( ) . toString ( ) ) ;
}
}
}
}
/ * *
* Initialize the application . This method has to be overridden by implementations .
* /
public abstract void simpleInitApp ( ) ;
/ * *
* Destroy the application ( release all resources ) .
* /
public void destroy ( ) {
if ( VRhardware ! = null ) {
VRhardware . destroy ( ) ;
VRhardware = null ;
}
DISABLE_VR = true ;
stateManager . cleanup ( ) ;
destroyInput ( ) ;
if ( audioRenderer ! = null )
audioRenderer . cleanup ( ) ;
timer . reset ( ) ;
Runtime . getRuntime ( ) . exit ( 0 ) ;
}
protected void destroyInput ( ) {
if ( mouseInput ! = null )
mouseInput . destroy ( ) ;
if ( keyInput ! = null )
keyInput . destroy ( ) ;
if ( joyInput ! = null )
joyInput . destroy ( ) ;
if ( touchInput ! = null )
touchInput . destroy ( ) ;
inputManager = null ;
}
@Override
public ViewPort getGuiViewPort ( ) {
return guiViewPort ;
}
@Override
public ViewPort getViewPort ( ) {
return viewPort ;
}
@Override
public < V > Future < V > enqueue ( Callable < V > callable ) {
AppTask < V > task = new AppTask < V > ( callable ) ;
taskQueue . add ( task ) ;
return task ;
}
/ * *
* Enqueues a runnable object to execute in the jME3
* rendering thread .
* < p >
* Runnables are executed right at the beginning of the main loop .
* They are executed even if the application is currently paused
* or out of focus .
*
* @param runnable The runnable to run in the main jME3 thread
* /
public void enqueue ( Runnable runnable ) {
enqueue ( new RunnableWrapper ( runnable ) ) ;
}
private class RunnableWrapper implements Callable < Object > {
private final Runnable runnable ;
public RunnableWrapper ( Runnable runnable ) {
this . runnable = runnable ;
}
@Override
public Object call ( ) {
runnable . run ( ) ;
return null ;
}
}
/ * *
* Requests the context to close , shutting down the main loop
* and making necessary cleanup operations .
*
* Same as calling stop ( false )
*
* @see # stop ( boolean )
* /
@Override
public void stop ( ) {
stop ( false ) ;
}
/ * *
* Requests the context to close , shutting down the main loop
* and making necessary cleanup operations .
* After the application has stopped , it cannot be used anymore .
* /
@Override
public void stop ( boolean waitFor ) {
logger . log ( Level . FINE , "Closing application: {0}" , getClass ( ) . getName ( ) ) ;
context . destroy ( waitFor ) ;
}
/ * *
* Restarts the context , applying any changed settings .
* < p >
* Changes to the { @link AppSettings } of this Application are not
* applied immediately ; calling this method forces the context
* to restart , applying the new settings .
* /
@Override
public void restart ( ) {
context . setSettings ( settings ) ;
context . restart ( ) ;
}
/ * *
* Sets an AppProfiler hook that will be called back for
* specific steps within a single update frame . Value defaults
* to null .
* /
public void setAppProfiler ( AppProfiler prof ) {
return ;
}
/ * *
* Returns the current AppProfiler hook , or null if none is set .
* /
public AppProfiler getAppProfiler ( ) {
return null ;
}
}