|
@@ -0,0 +1,1453 @@
|
|
|
+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.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.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 jopenvr.JOpenVRLibrary;
|
|
|
+
|
|
|
+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.
|
|
|
+ * @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 abstract class VRApplication implements Application, SystemListener {
|
|
|
+
|
|
|
+
|
|
|
+ private static final Logger logger = Logger.getLogger(LegacyApplication.class.getName());
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The default FOV.
|
|
|
+ */
|
|
|
+ public static float DEFAULT_FOV = 108f;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The default aspect ratio.
|
|
|
+ */
|
|
|
+ public static float DEFAULT_ASPECT = 1f;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Is the application is based on OSVR (default is <code>false</code>).
|
|
|
+ */
|
|
|
+ public static boolean CONSTRUCT_WITH_OSVR = false;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Is the application has not to start within VR mode (default is <code>false</code>).
|
|
|
+ */
|
|
|
+ public static 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 static String OS;
|
|
|
+ private static VRAPI VRhardware;
|
|
|
+ private static Camera dummyCam;
|
|
|
+ private static VRViewManager VRviewmanager;
|
|
|
+ private static VRApplication mainApp;
|
|
|
+ private static Spatial observer;
|
|
|
+ private static boolean VRSupportedOS, forceVR, disableSwapBuffers = true, tryOpenGL3 = true, seated, nogui, instanceVR, 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 static float distanceOfOptimization = 0f;
|
|
|
+
|
|
|
+ private static float resMult = 1f;
|
|
|
+
|
|
|
+ private static boolean useCompositor = true, compositorOS;
|
|
|
+ private final String RESET_HMD = "ResetHMD";
|
|
|
+
|
|
|
+ // no longer using LwjglCanvas, and this sometimes broke the graphics settings
|
|
|
+ /*static {
|
|
|
+ if( VR_IsHmdPresent() != 0 ) {
|
|
|
+ System.setProperty("sun.java2d.opengl", "True");
|
|
|
+ }
|
|
|
+ } */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the distance of optimization.
|
|
|
+ * @return the distance of optimization.
|
|
|
+ */
|
|
|
+ /*
|
|
|
+ public static float getOptimizationDistance() {
|
|
|
+ return distanceOfOptimization;
|
|
|
+ }
|
|
|
+ */
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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( VRviewmanager != null ) VRviewmanager.setResolutionMultiplier(resMult);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Is the SteamVR compositor is active.
|
|
|
+ * @return <code>true</code> if the SteamVR compositor is active and <code>false</code> otherwise.
|
|
|
+ */
|
|
|
+ public static 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 static 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();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Create a new VR application and attach the given {@link AppState app states}.
|
|
|
+ * @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.
|
|
|
+ */
|
|
|
+ public VRApplication() {
|
|
|
+ super();
|
|
|
+ initStateManager();
|
|
|
+
|
|
|
+ rootNode = new Node("root");
|
|
|
+ guiNode = new Node("guiNode");
|
|
|
+ guiNode.setQueueBucket(Bucket.Gui);
|
|
|
+ guiNode.setCullHint(CullHint.Never);
|
|
|
+ dummyCam = new Camera();
|
|
|
+ mainApp = this;
|
|
|
+
|
|
|
+ // 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 ) {
|
|
|
+ logger.config("Initializing OSVR...");
|
|
|
+ VRhardware = new OSVR();
|
|
|
+ } else {
|
|
|
+ logger.config("Initializing OpenVR...");
|
|
|
+ VRhardware = new OpenVR();
|
|
|
+ }
|
|
|
+ if( VRhardware.initialize() ) {
|
|
|
+ setPauseOnLostFocus(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ 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() && VRviewmanager != null && VRviewmanager.getCamLeft() != 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() {
|
|
|
+ // 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 = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
|
|
|
+
|
|
|
+ if( isInVR() && !compositorAllowed() ) {
|
|
|
+ // "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();
|
|
|
+ return;
|
|
|
+ } catch(Exception e) {
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if( !isInVR() ) {
|
|
|
+ // not in VR, show settings dialog
|
|
|
+ if( Platform.get() != Platform.MACOSX ) {
|
|
|
+ if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {
|
|
|
+ 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 {
|
|
|
+ // 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 ) {
|
|
|
+ // disable multisampling, which is more likely to break things than be useful
|
|
|
+ settings.setSamples(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ // set opengl mode
|
|
|
+ if( tryOpenGL3 ) {
|
|
|
+ settings.setRenderer(AppSettings.LWJGL_OPENGL3);
|
|
|
+ } else {
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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:
|
|
|
+ VRGuiManager._enableGuiOverdraw(value);
|
|
|
+ break;
|
|
|
+ case SET_GUI_CURVED_SURFACE:
|
|
|
+ VRGuiManager._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:
|
|
|
+ VRApplication.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( VRApplication.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 static 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 static boolean isSeatedExperience() {
|
|
|
+ return seated;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Reset headset pose if seating experience.
|
|
|
+ */
|
|
|
+ public static void resetSeatedPose(){
|
|
|
+ if( VRSupportedOS == false || isSeatedExperience() == false ) return;
|
|
|
+ VRhardware.reset();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the VR dedicated input.
|
|
|
+ * @return the VR dedicated input.
|
|
|
+ */
|
|
|
+ public static VRInputAPI getVRinput() {
|
|
|
+ if( VRhardware == null ) return null;
|
|
|
+ return VRhardware.getVRinput();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 static 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 static boolean isInVR() {
|
|
|
+ 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 static void moveScreenProcessingToVR() {
|
|
|
+ if( isInVR() ) {
|
|
|
+ VRviewmanager.moveScreenProcessingToEyes();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the VR underlying hardware.
|
|
|
+ * @return the VR underlying hardware.
|
|
|
+ */
|
|
|
+ public static VRAPI getVRHardware() {
|
|
|
+ return VRhardware;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the GUI node from the application.
|
|
|
+ * @return the GUI node from the application.
|
|
|
+ */
|
|
|
+ public Node getGuiNode(){
|
|
|
+ return guiNode;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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 static 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 static Object getObserver() {
|
|
|
+ if( observer == null ) {
|
|
|
+ return mainApp.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 static void setObserver(Spatial observer) {
|
|
|
+ VRApplication.observer = observer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the VR view manager.
|
|
|
+ * @return the VR view manager.
|
|
|
+ */
|
|
|
+ public static VRViewManager getVRViewManager() {
|
|
|
+ return VRviewmanager;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ 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 static Quaternion getFinalObserverRotation() {
|
|
|
+ if( VRviewmanager == null ) {
|
|
|
+ if( VRApplication.observer == null ) {
|
|
|
+ return mainApp.getCamera().getRotation();
|
|
|
+ } else return VRApplication.observer.getWorldRotation();
|
|
|
+ }
|
|
|
+ if( VRApplication.observer == null ) {
|
|
|
+ tempq.set(dummyCam.getRotation());
|
|
|
+ } else {
|
|
|
+ tempq.set(VRApplication.observer.getWorldRotation());
|
|
|
+ }
|
|
|
+ return tempq.multLocal(VRhardware.getOrientation());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the observer final position within the scene.
|
|
|
+ * @return the observer position.
|
|
|
+ * @see #getFinalObserverRotation()
|
|
|
+ */
|
|
|
+ public static Vector3f getFinalObserverPosition() {
|
|
|
+ if( VRviewmanager == null ) {
|
|
|
+ if( VRApplication.observer == null ) {
|
|
|
+ return mainApp.getCamera().getLocation();
|
|
|
+ } else return VRApplication.observer.getWorldTranslation();
|
|
|
+ }
|
|
|
+ Vector3f pos = VRhardware.getPosition();
|
|
|
+ if( VRApplication.observer == null ) {
|
|
|
+ dummyCam.getRotation().mult(pos, pos);
|
|
|
+ return pos.addLocal(dummyCam.getLocation());
|
|
|
+ } else {
|
|
|
+ VRApplication.observer.getWorldRotation().mult(pos, pos);
|
|
|
+ return pos.addLocal(VRApplication.observer.getWorldTranslation());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the VR headset height from the ground.
|
|
|
+ * @param amount the VR headset height from the ground.
|
|
|
+ * @see #getVRHeightAdjustment()
|
|
|
+ */
|
|
|
+ public static void setVRHeightAdjustment(float amount) {
|
|
|
+ if( VRviewmanager != null ) VRviewmanager.setHeightAdjustment(amount);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the VR headset height from the ground.
|
|
|
+ * @return the VR headset height from the ground.
|
|
|
+ * @see #setVRHeightAdjustment(float)
|
|
|
+ */
|
|
|
+ public static float getVRHeightAdjustment() {
|
|
|
+ if( VRviewmanager != null ) return VRviewmanager.getHeightAdjustment();
|
|
|
+ return 0f;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the VR headset left viewport.
|
|
|
+ * @return the VR headset left viewport.
|
|
|
+ * @see #getRightViewPort()
|
|
|
+ */
|
|
|
+ public static ViewPort getLeftViewPort() {
|
|
|
+ if( VRviewmanager == null ) return mainApp.getViewPort();
|
|
|
+ return VRviewmanager.getViewPortLeft();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the VR headset right viewport.
|
|
|
+ * @return the VR headset right viewport.
|
|
|
+ * @see #getLeftViewPort()
|
|
|
+ */
|
|
|
+ public static ViewPort getRightViewPort() {
|
|
|
+ if( VRviewmanager == null ) return mainApp.getViewPort();
|
|
|
+ return VRviewmanager.getViewPortRight();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Set the background color for both left and right view ports.
|
|
|
+ * @param clr the background color.
|
|
|
+ */
|
|
|
+ public static void setBackgroundColors(ColorRGBA clr) {
|
|
|
+ if( VRviewmanager == null ) {
|
|
|
+ mainApp.getViewPort().setBackgroundColor(clr);
|
|
|
+ } else if( VRviewmanager.getViewPortLeft() != null ) {
|
|
|
+ VRviewmanager.getViewPortLeft().setBackgroundColor(clr);
|
|
|
+ if( VRviewmanager.getViewPortRight() != null ) VRviewmanager.getViewPortRight().setBackgroundColor(clr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Get the instance of VR application that is currently running.
|
|
|
+ * @return the instance of VR application that is currently running.
|
|
|
+ */
|
|
|
+ public static VRApplication getMainVRApp() {
|
|
|
+ return mainApp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 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);
|
|
|
+
|
|
|
+ rootNode.updateLogicalState(tpf);
|
|
|
+ guiNode.updateLogicalState(tpf);
|
|
|
+
|
|
|
+ rootNode.updateGeometricState();
|
|
|
+
|
|
|
+ if( VRApplication.isInVR() == false || VRGuiManager.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();
|
|
|
+ }
|
|
|
+
|
|
|
+ // render states
|
|
|
+ stateManager.render(renderManager);
|
|
|
+
|
|
|
+ // update VR pose & cameras
|
|
|
+ if( VRviewmanager != null ) {
|
|
|
+ VRviewmanager.update(tpf);
|
|
|
+ } else if( VRApplication.observer != null ) {
|
|
|
+ getCamera().setFrame(VRApplication.observer.getWorldTranslation(), VRApplication.observer.getWorldRotation());
|
|
|
+ }
|
|
|
+
|
|
|
+ renderManager.render(tpf, context.isRenderable());
|
|
|
+ simpleRender(renderManager);
|
|
|
+ stateManager.postRender();
|
|
|
+
|
|
|
+ // update compositor?
|
|
|
+ if( VRviewmanager != null ) {
|
|
|
+ VRviewmanager.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() {
|
|
|
+ initialize_internal();
|
|
|
+ cam.setFrustumFar(fFar);
|
|
|
+ cam.setFrustumNear(fNear);
|
|
|
+ dummyCam = cam.clone();
|
|
|
+ if( isInVR() ) {
|
|
|
+ if( VRhardware != null ) {
|
|
|
+ VRhardware.initVRCompositor(compositorAllowed());
|
|
|
+ }
|
|
|
+ VRviewmanager = new VRViewManager(this);
|
|
|
+ VRviewmanager.setResolutionMultiplier(resMult);
|
|
|
+ inputManager.addMapping(RESET_HMD, new KeyTrigger(KeyInput.KEY_F9));
|
|
|
+ setLostFocusBehavior(LostFocusBehavior.Disabled);
|
|
|
+ } else {
|
|
|
+ viewPort.attachScene(rootNode);
|
|
|
+ guiViewPort.attachScene(guiNode);
|
|
|
+ }
|
|
|
+
|
|
|
+ if( VRviewmanager != null ) {
|
|
|
+ VRviewmanager.initialize(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ simpleInitApp();
|
|
|
+
|
|
|
+ // any filters created, move them now
|
|
|
+ if( VRviewmanager != null ) {
|
|
|
+ VRviewmanager.moveScreenProcessingToEyes();
|
|
|
+
|
|
|
+ // print out camera information
|
|
|
+ if( isInVR() ) {
|
|
|
+ logger.info("VR Initialization Information");
|
|
|
+ if( VRviewmanager.getCamLeft() != null ){
|
|
|
+ logger.info("camLeft: " + VRviewmanager.getCamLeft().toString());
|
|
|
+ }
|
|
|
+
|
|
|
+ if( VRviewmanager.getCamRight() != null ){
|
|
|
+ logger.info("camRight: " + VRviewmanager.getCamRight().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;
|
|
|
+ }
|
|
|
+}
|