/* * Copyright (c) 2009-2010 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'jMonkeyEngine' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.app; 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.*; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; import com.jme3.system.JmeContext.Type; import com.jme3.system.*; import java.net.MalformedURLException; import java.net.URL; 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; /** * The Application class represents an instance of a * real-time 3D rendering jME application. * * An Application provides all the tools that are commonly used in jME3 * applications. * * jME3 applications should extend this class and call start() to begin the * application. * */ public class Application implements SystemListener { private static final Logger logger = Logger.getLogger(Application.class.getName()); protected AssetManager assetManager; protected AudioRenderer audioRenderer; protected Renderer renderer; protected RenderManager renderManager; protected ViewPort viewPort; protected ViewPort guiViewPort; protected JmeContext context; protected AppSettings settings; protected Timer timer = new NanoTimer(); protected Camera cam; protected Listener listener; protected boolean inputEnabled = true; protected boolean pauseOnFocus = true; protected float speed = 1f; protected boolean paused = false; protected MouseInput mouseInput; protected KeyInput keyInput; protected JoyInput joyInput; protected TouchInput touchInput; protected InputManager inputManager; protected AppStateManager stateManager; private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue>(); /** * Create a new instance of Application. */ public Application(){ initStateManager(); } /** * Returns true if pause on lost focus is enabled, false otherwise. * * @return true if pause on lost focus is enabled * * @see #setPauseOnLostFocus(boolean) */ public boolean isPauseOnLostFocus() { return pauseOnFocus; } /** * Enable or disable pause on lost focus. *

* 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 True to enable pause on lost focus, false * otherwise. */ public void setPauseOnLostFocus(boolean pauseOnLostFocus) { this.pauseOnFocus = pauseOnLostFocus; } @Deprecated public void setAssetManager(AssetManager assetManager){ if (this.assetManager != null) throw new IllegalStateException("Can only set asset manager" + " before initialization."); this.assetManager = assetManager; } private void initAssetManager(){ if (settings != null){ String assetCfg = settings.getString("AssetConfigURL"); if (assetCfg != null){ URL url = null; try { url = new URL(assetCfg); } catch (MalformedURLException ex) { } if (url == null) { url = Application.class.getClassLoader().getResource(assetCfg); if (url == null) { logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg); return; } } assetManager = JmeSystem.newAssetManager(url); } } if (assetManager == null){ assetManager = JmeSystem.newAssetManager( Thread.currentThread().getContextClassLoader() .getResource("com/jme3/asset/Desktop.cfg")); } } /** * Set the display settings to define the display created. *

* Examples of display parameters include display pixel width and height, * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency. * 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 Timer implementation that will be used for calculating * frame times. By default, Application will use the Timer as returned * by the current JmeContext implementation. */ public void setTimer(Timer timer){ this.timer = timer; if (timer != null) { timer.reset(); } if (renderManager != null) { renderManager.setTimer(timer); } } public Timer getTimer(){ return timer; } 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() != 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()); } /** * @return The {@link AssetManager asset manager} for this application. */ public AssetManager getAssetManager(){ return assetManager; } /** * @return the {@link InputManager input manager}. */ public InputManager getInputManager(){ return inputManager; } /** * @return the {@link AppStateManager app state manager} */ public AppStateManager getStateManager() { return stateManager; } /** * @return the {@link RenderManager render manager} */ public RenderManager getRenderManager() { return renderManager; } /** * @return The {@link Renderer renderer} for the application */ public Renderer getRenderer(){ return renderer; } /** * @return The {@link AudioRenderer audio renderer} for the application */ public AudioRenderer getAudioRenderer() { return audioRenderer; } /** * @return The {@link Listener listener} object for audio */ public Listener getListener() { return listener; } /** * @return The {@link JmeContext display context} for the application */ public JmeContext getContext(){ return context; } /** * @return The {@link Camera camera} for the application */ public Camera getCamera(){ return cam; } /** * Starts the application in {@link Type#Display display} mode. * * @see #start(com.jme3.system.JmeContext.Type) */ public void start(){ start(JmeContext.Type.Display); } /** * Starts the application. * Creating a rendering context and executing * the main loop in a separate thread. */ public void start(JmeContext.Type contextType){ 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()); context = JmeSystem.newContext(settings, contextType); context.setSystemListener(this); context.create(false); } /** * Initializes the application's canvas for use. *

* After calling this method, cast the {@link #getContext() context} to * {@link JmeCanvasContext}, * then acquire the canvas with {@link JmeCanvasContext#getCanvas() } * and attach it to an AWT/Swing Frame. * The rendering thread will start when the canvas becomes visible on * screen, however if you wish to start the context immediately you * may call {@link #startCanvas() } to force the rendering thread * to start. * * @see JmeCanvasContext * @see Type#Canvas */ public void createCanvas(){ if (context != null && context.isCreated()){ logger.warning("createCanvas() called when application already created!"); return; } if (settings == null){ settings = new AppSettings(true); } logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); context = JmeSystem.newContext(settings, JmeContext.Type.Canvas); context.setSystemListener(this); } /** * Starts the rendering thread after createCanvas() has been called. *

* Same as calling startCanvas(false) * * @see #startCanvas(boolean) */ public void startCanvas(){ startCanvas(false); } /** * Starts the rendering thread after createCanvas() has been called. *

* Calling this method is optional, the canvas will start automatically * when it becomes visible. * * @param waitFor If true, the current thread will block until the * rendering thread is running */ public void startCanvas(boolean waitFor){ context.create(waitFor); } /** * Internal use only. */ public void reshape(int w, int h){ renderManager.notifyReshape(w, h); } /** * Restarts the context, applying any changed settings. *

* Changes to the {@link AppSettings} of this Application are not * applied immediately; calling this method forces the context * to restart, applying the new settings. */ public void restart(){ context.setSettings(settings); context.restart(); } /** * Requests the context to close, shutting down the main loop * and making necessary cleanup operations. * * Same as calling stop(false) * * @see #stop(boolean) */ 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. */ public void stop(boolean waitFor){ logger.log(Level.FINE, "Closing application: {0}", getClass().getName()); context.destroy(waitFor); } /** * Do not call manually. * Callback from ContextListener. *

* Initializes the Application, 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. */ public void initialize(){ 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.. } /** * Internal use only. */ public void handleError(String errMsg, Throwable t){ logger.log(Level.SEVERE, errMsg, t); // user should add additional code to handle the error. stop(); // stop the application } /** * Internal use only. */ public void gainFocus(){ if (pauseOnFocus) { paused = false; context.setAutoFlushFrames(true); if (inputManager != null) { inputManager.reset(); } } } /** * Internal use only. */ public void loseFocus(){ if (pauseOnFocus){ paused = true; context.setAutoFlushFrames(false); } } /** * Internal use only. */ public void requestClose(boolean esc){ context.destroy(false); } /** * Enqueues a task/callable object to execute in the jME3 * rendering thread. *

* Callables are executed right at the beginning of the main loop. * They are executed even if the application is currently paused * or out of focus. */ public Future enqueue(Callable callable) { AppTask task = new AppTask(callable); taskQueue.add(task); return task; } /** * Do not call manually. * Callback from ContextListener. */ public void update(){ // Make sure the audio renderer is available to callables AudioContext.setAudioRenderer(audioRenderer); AppTask task = taskQueue.poll(); toploop: do { if (task == null) break; while (task.isCancelled()) { task = taskQueue.poll(); if (task == null) break toploop; } task.invoke(); } while (((task = taskQueue.poll()) != null)); /* I think the above is really just doing this: AppTask task; while( (task = taskQueue.poll()) != null ) { if (!task.isCancelled()) { task.invoke(); } } //...but it's hard to say for sure. It's so twisted //up that I don't trust my eyes. -pspeed */ if (speed == 0 || paused) return; timer.update(); if (inputEnabled){ inputManager.update(timer.getTimePerFrame()); } if (audioRenderer != null){ audioRenderer.update(timer.getTimePerFrame()); } // user code here.. } 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; } /** * Do not call manually. * Callback from ContextListener. */ public void destroy(){ stateManager.cleanup(); destroyInput(); if (audioRenderer != null) audioRenderer.cleanup(); timer.reset(); } /** * @return The GUI viewport. Which is used for the on screen * statistics and FPS. */ public ViewPort getGuiViewPort() { return guiViewPort; } public ViewPort getViewPort() { return viewPort; } }