A complete 3D game development suite written purely in Java.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
jmonkeyengine/jme3-vr/src/main/java/com/jme3/app/VRApplication.java

1537 lines
51 KiB

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;
}
}