2 * Copyright (c) 2009-2010 jMonkeyEngine
\r
3 * All rights reserved.
\r
5 * Redistribution and use in source and binary forms, with or without
\r
6 * modification, are permitted provided that the following conditions are
\r
9 * * Redistributions of source code must retain the above copyright
\r
10 * notice, this list of conditions and the following disclaimer.
\r
12 * * Redistributions in binary form must reproduce the above copyright
\r
13 * notice, this list of conditions and the following disclaimer in the
\r
14 * documentation and/or other materials provided with the distribution.
\r
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
\r
17 * may be used to endorse or promote products derived from this software
\r
18 * without specific prior written permission.
\r
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
\r
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
\r
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
\r
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
\r
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
\r
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
\r
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
\r
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
\r
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
\r
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
\r
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\r
33 package com.jme3.app;
\r
35 import com.jme3.app.state.AppStateManager;
\r
36 import com.jme3.input.JoyInput;
\r
37 import com.jme3.input.KeyInput;
\r
38 import com.jme3.input.MouseInput;
\r
39 import com.jme3.input.TouchInput;
\r
40 import com.jme3.math.Vector3f;
\r
41 import com.jme3.renderer.Camera;
\r
42 import com.jme3.renderer.Renderer;
\r
43 import com.jme3.asset.AssetManager;
\r
44 import com.jme3.audio.AudioContext;
\r
45 import com.jme3.audio.AudioRenderer;
\r
46 import com.jme3.audio.Listener;
\r
47 import com.jme3.input.InputManager;
\r
48 import com.jme3.renderer.RenderManager;
\r
49 import com.jme3.renderer.ViewPort;
\r
50 import com.jme3.system.AppSettings;
\r
51 import com.jme3.system.JmeContext;
\r
52 import java.net.MalformedURLException;
\r
53 import java.net.URL;
\r
54 import java.util.concurrent.Callable;
\r
55 import java.util.concurrent.ConcurrentLinkedQueue;
\r
56 import java.util.concurrent.Future;
\r
57 import java.util.logging.Level;
\r
58 import java.util.logging.Logger;
\r
59 import com.jme3.system.JmeContext.Type;
\r
60 import com.jme3.system.JmeSystem;
\r
61 import com.jme3.system.SystemListener;
\r
62 import com.jme3.system.Timer;
\r
65 * The <code>Application</code> class represents an instance of a
\r
66 * real-time 3D rendering jME application.
\r
68 * An <code>Application</code> provides all the tools that are commonly used in jME3
\r
71 * jME3 applications should extend this class and call start() to begin the
\r
75 public class Application implements SystemListener {
\r
77 private static final Logger logger = Logger.getLogger(Application.class.getName());
\r
79 protected AssetManager assetManager;
\r
81 protected AudioRenderer audioRenderer;
\r
82 protected Renderer renderer;
\r
83 protected RenderManager renderManager;
\r
84 protected ViewPort viewPort;
\r
85 protected ViewPort guiViewPort;
\r
87 protected JmeContext context;
\r
88 protected AppSettings settings;
\r
89 protected Timer timer;
\r
90 protected Camera cam;
\r
91 protected Listener listener;
\r
93 protected boolean inputEnabled = true;
\r
94 protected boolean pauseOnFocus = true;
\r
95 protected float speed = 1f;
\r
96 protected boolean paused = false;
\r
97 protected MouseInput mouseInput;
\r
98 protected KeyInput keyInput;
\r
99 protected JoyInput joyInput;
\r
100 protected TouchInput touchInput;
\r
101 protected InputManager inputManager;
\r
102 protected AppStateManager stateManager;
\r
104 private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
\r
107 * Create a new instance of <code>Application</code>.
\r
109 public Application(){
\r
113 * Returns true if pause on lost focus is enabled, false otherwise.
\r
115 * @return true if pause on lost focus is enabled
\r
117 * @see #setPauseOnLostFocus(boolean)
\r
119 public boolean isPauseOnLostFocus() {
\r
120 return pauseOnFocus;
\r
124 * Enable or disable pause on lost focus.
\r
126 * By default, pause on lost focus is enabled.
\r
127 * If enabled, the application will stop updating
\r
128 * when it loses focus or becomes inactive (e.g. alt-tab).
\r
129 * For online or real-time applications, this might not be preferable,
\r
130 * so this feature should be set to disabled. For other applications,
\r
131 * it is best to keep it on so that CPU usage is not used when
\r
134 * @param pauseOnLostFocus True to enable pause on lost focus, false
\r
137 public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
\r
138 this.pauseOnFocus = pauseOnLostFocus;
\r
142 public void setAssetManager(AssetManager assetManager){
\r
143 if (this.assetManager != null)
\r
144 throw new IllegalStateException("Can only set asset manager"
\r
145 + " before initialization.");
\r
147 this.assetManager = assetManager;
\r
150 private void initAssetManager(){
\r
151 if (settings != null){
\r
152 String assetCfg = settings.getString("AssetConfigURL");
\r
153 if (assetCfg != null){
\r
156 url = new URL(assetCfg);
\r
157 } catch (MalformedURLException ex) {
\r
160 url = Application.class.getClassLoader().getResource(assetCfg);
\r
162 logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
\r
166 assetManager = JmeSystem.newAssetManager(url);
\r
169 if (assetManager == null){
\r
170 assetManager = JmeSystem.newAssetManager(
\r
171 Thread.currentThread().getContextClassLoader()
\r
172 .getResource("com/jme3/asset/Desktop.cfg"));
\r
177 * Set the display settings to define the display created.
\r
179 * Examples of display parameters include display pixel width and height,
\r
180 * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.
\r
181 * If this method is called while the application is already running, then
\r
182 * {@link #restart() } must be called to apply the settings to the display.
\r
184 * @param settings The settings to set.
\r
186 public void setSettings(AppSettings settings){
\r
187 this.settings = settings;
\r
188 if (context != null && settings.useInput() != inputEnabled){
\r
189 // may need to create or destroy input based
\r
190 // on settings change
\r
191 inputEnabled = !inputEnabled;
\r
198 inputEnabled = settings.useInput();
\r
203 * Sets the Timer implementation that will be used for calculating
\r
204 * frame times. By default, Application will use the Timer as returned
\r
205 * by the current JmeContext implementation.
\r
207 public void setTimer(Timer timer){
\r
208 this.timer = timer;
\r
210 if (timer != null) {
\r
214 if (renderManager != null) {
\r
215 renderManager.setTimer(timer);
\r
219 private void initDisplay(){
\r
220 // aquire important objects
\r
221 // from the context
\r
222 settings = context.getSettings();
\r
224 // Only reset the timer if a user has not already provided one
\r
225 if (timer == null) {
\r
226 timer = context.getTimer();
\r
229 renderer = context.getRenderer();
\r
232 private void initAudio(){
\r
233 if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
\r
234 audioRenderer = JmeSystem.newAudioRenderer(settings);
\r
235 audioRenderer.initialize();
\r
236 AudioContext.setAudioRenderer(audioRenderer);
\r
238 listener = new Listener();
\r
239 audioRenderer.setListener(listener);
\r
244 * Creates the camera to use for rendering. Default values are perspective
\r
245 * projection with 45° field of view, with near and far values 1 and 1000
\r
246 * units respectively.
\r
248 private void initCamera(){
\r
249 cam = new Camera(settings.getWidth(), settings.getHeight());
\r
251 cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
\r
252 cam.setLocation(new Vector3f(0f, 0f, 10f));
\r
253 cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
\r
255 renderManager = new RenderManager(renderer);
\r
256 //Remy - 09/14/2010 setted the timer in the renderManager
\r
257 renderManager.setTimer(timer);
\r
258 viewPort = renderManager.createMainView("Default", cam);
\r
259 viewPort.setClearFlags(true, true, true);
\r
261 // Create a new cam for the gui
\r
262 Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
\r
263 guiViewPort = renderManager.createPostView("Gui Default", guiCam);
\r
264 guiViewPort.setClearFlags(false, false, false);
\r
268 * Initializes mouse and keyboard input. Also
\r
269 * initializes joystick input if joysticks are enabled in the
\r
272 private void initInput(){
\r
273 mouseInput = context.getMouseInput();
\r
274 if (mouseInput != null)
\r
275 mouseInput.initialize();
\r
277 keyInput = context.getKeyInput();
\r
278 if (keyInput != null)
\r
279 keyInput.initialize();
\r
281 touchInput = context.getTouchInput();
\r
282 if (touchInput != null)
\r
283 touchInput.initialize();
\r
285 if (!settings.getBoolean("DisableJoysticks")){
\r
286 joyInput = context.getJoyInput();
\r
287 if (joyInput != null)
\r
288 joyInput.initialize();
\r
291 inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
\r
294 private void initStateManager(){
\r
295 stateManager = new AppStateManager(this);
\r
299 * @return The {@link AssetManager asset manager} for this application.
\r
301 public AssetManager getAssetManager(){
\r
302 return assetManager;
\r
306 * @return the {@link InputManager input manager}.
\r
308 public InputManager getInputManager(){
\r
309 return inputManager;
\r
313 * @return the {@link AppStateManager app state manager}
\r
315 public AppStateManager getStateManager() {
\r
316 return stateManager;
\r
320 * @return the {@link RenderManager render manager}
\r
322 public RenderManager getRenderManager() {
\r
323 return renderManager;
\r
327 * @return The {@link Renderer renderer} for the application
\r
329 public Renderer getRenderer(){
\r
334 * @return The {@link AudioRenderer audio renderer} for the application
\r
336 public AudioRenderer getAudioRenderer() {
\r
337 return audioRenderer;
\r
341 * @return The {@link Listener listener} object for audio
\r
343 public Listener getListener() {
\r
348 * @return The {@link JmeContext display context} for the application
\r
350 public JmeContext getContext(){
\r
355 * @return The {@link Camera camera} for the application
\r
357 public Camera getCamera(){
\r
362 * Starts the application in {@link Type#Display display} mode.
\r
364 * @see #start(com.jme3.system.JmeContext.Type)
\r
366 public void start(){
\r
367 start(JmeContext.Type.Display);
\r
371 * Starts the application.
\r
372 * Creating a rendering context and executing
\r
373 * the main loop in a separate thread.
\r
375 public void start(JmeContext.Type contextType){
\r
376 if (context != null && context.isCreated()){
\r
377 logger.warning("start() called when application already created!");
\r
381 if (settings == null){
\r
382 settings = new AppSettings(true);
\r
385 logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
\r
386 context = JmeSystem.newContext(settings, contextType);
\r
387 context.setSystemListener(this);
\r
388 context.create(false);
\r
392 * Initializes the application's canvas for use.
\r
394 * After calling this method, cast the {@link #getContext() context} to
\r
395 * {@link JmeCanvasContext},
\r
396 * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
\r
397 * and attach it to an AWT/Swing Frame.
\r
398 * The rendering thread will start when the canvas becomes visible on
\r
399 * screen, however if you wish to start the context immediately you
\r
400 * may call {@link #startCanvas() } to force the rendering thread
\r
403 * @see JmeCanvasContext
\r
406 public void createCanvas(){
\r
407 if (context != null && context.isCreated()){
\r
408 logger.warning("createCanvas() called when application already created!");
\r
412 if (settings == null){
\r
413 settings = new AppSettings(true);
\r
416 logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
\r
417 context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
\r
418 context.setSystemListener(this);
\r
422 * Starts the rendering thread after createCanvas() has been called.
\r
424 * Same as calling startCanvas(false)
\r
426 * @see #startCanvas(boolean)
\r
428 public void startCanvas(){
\r
429 startCanvas(false);
\r
433 * Starts the rendering thread after createCanvas() has been called.
\r
435 * Calling this method is optional, the canvas will start automatically
\r
436 * when it becomes visible.
\r
438 * @param waitFor If true, the current thread will block until the
\r
439 * rendering thread is running
\r
441 public void startCanvas(boolean waitFor){
\r
442 context.create(waitFor);
\r
446 * Internal use only.
\r
448 public void reshape(int w, int h){
\r
449 renderManager.notifyReshape(w, h);
\r
453 * Restarts the context, applying any changed settings.
\r
455 * Changes to the {@link AppSettings} of this Application are not
\r
456 * applied immediately; calling this method forces the context
\r
457 * to restart, applying the new settings.
\r
459 public void restart(){
\r
460 context.setSettings(settings);
\r
465 * Requests the context to close, shutting down the main loop
\r
466 * and making necessary cleanup operations.
\r
468 * Same as calling stop(false)
\r
470 * @see #stop(boolean)
\r
472 public void stop(){
\r
477 * Requests the context to close, shutting down the main loop
\r
478 * and making necessary cleanup operations.
\r
479 * After the application has stopped, it cannot be used anymore.
\r
481 public void stop(boolean waitFor){
\r
482 logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
\r
483 context.destroy(waitFor);
\r
487 * Do not call manually.
\r
488 * Callback from ContextListener.
\r
490 * Initializes the <code>Application</code>, by creating a display and
\r
491 * default camera. If display settings are not specified, a default
\r
492 * 640x480 display is created. Default values are used for the camera;
\r
493 * perspective projection with 45° field of view, with near
\r
494 * and far values 1 and 1000 units respectively.
\r
496 public void initialize(){
\r
497 if (assetManager == null){
\r
498 initAssetManager();
\r
508 initStateManager();
\r
510 // update timer so that the next delta is not too large
\r
514 // user code here..
\r
518 * Internal use only.
\r
520 public void handleError(String errMsg, Throwable t){
\r
521 logger.log(Level.SEVERE, errMsg, t);
\r
522 // user should add additional code to handle the error.
\r
523 stop(); // stop the application
\r
527 * Internal use only.
\r
529 public void gainFocus(){
\r
530 if (pauseOnFocus) {
\r
532 context.setAutoFlushFrames(true);
\r
533 if (inputManager != null) {
\r
534 inputManager.reset();
\r
540 * Internal use only.
\r
542 public void loseFocus(){
\r
545 context.setAutoFlushFrames(false);
\r
550 * Internal use only.
\r
552 public void requestClose(boolean esc){
\r
553 context.destroy(false);
\r
557 * Enqueues a task/callable object to execute in the jME3
\r
558 * rendering thread.
\r
560 * Callables are executed right at the beginning of the main loop.
\r
561 * They are executed even if the application is currently paused
\r
564 public <V> Future<V> enqueue(Callable<V> callable) {
\r
565 AppTask<V> task = new AppTask<V>(callable);
\r
566 taskQueue.add(task);
\r
571 * Do not call manually.
\r
572 * Callback from ContextListener.
\r
574 public void update(){
\r
575 // Make sure the audio renderer is available to callables
\r
576 AudioContext.setAudioRenderer(audioRenderer);
\r
578 AppTask<?> task = taskQueue.poll();
\r
580 if (task == null) break;
\r
581 while (task.isCancelled()) {
\r
582 task = taskQueue.poll();
\r
583 if (task == null) break toploop;
\r
586 } while (((task = taskQueue.poll()) != null));
\r
588 if (speed == 0 || paused)
\r
594 inputManager.update(timer.getTimePerFrame());
\r
597 if (audioRenderer != null){
\r
598 audioRenderer.update(timer.getTimePerFrame());
\r
601 // user code here..
\r
604 protected void destroyInput(){
\r
605 if (mouseInput != null)
\r
606 mouseInput.destroy();
\r
608 if (keyInput != null)
\r
609 keyInput.destroy();
\r
611 if (joyInput != null)
\r
612 joyInput.destroy();
\r
614 if (touchInput != null)
\r
615 touchInput.destroy();
\r
617 inputManager = null;
\r
621 * Do not call manually.
\r
622 * Callback from ContextListener.
\r
624 public void destroy(){
\r
625 stateManager.cleanup();
\r
628 if (audioRenderer != null)
\r
629 audioRenderer.cleanup();
\r
635 * @return The GUI viewport. Which is used for the on screen
\r
636 * statistics and FPS.
\r
638 public ViewPort getGuiViewPort() {
\r
639 return guiViewPort;
\r
642 public ViewPort getViewPort() {
\r