1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 package com.jme3.app; 34 35 import com.jme3.app.state.AppStateManager; 36 import com.jme3.asset.AssetManager; 37 import com.jme3.audio.AudioContext; 38 import com.jme3.audio.AudioRenderer; 39 import com.jme3.audio.Listener; 40 import com.jme3.input.*; 41 import com.jme3.math.Vector3f; 42 import com.jme3.renderer.Camera; 43 import com.jme3.renderer.RenderManager; 44 import com.jme3.renderer.Renderer; 45 import com.jme3.renderer.ViewPort; 46 import com.jme3.system.JmeContext.Type; 47 import com.jme3.system.*; 48 import java.net.MalformedURLException; 49 import java.net.URL; 50 import java.util.concurrent.Callable; 51 import java.util.concurrent.ConcurrentLinkedQueue; 52 import java.util.concurrent.Future; 53 import java.util.logging.Level; 54 import java.util.logging.Logger; 55 56 /** 57 * The <code>Application</code> class represents an instance of a 58 * real-time 3D rendering jME application. 59 * 60 * An <code>Application</code> provides all the tools that are commonly used in jME3 61 * applications. 62 * 63 * jME3 applications should extend this class and call start() to begin the 64 * application. 65 * 66 */ 67 public class Application implements SystemListener { 68 69 private static final Logger logger = Logger.getLogger(Application.class.getName()); 70 71 protected AssetManager assetManager; 72 73 protected AudioRenderer audioRenderer; 74 protected Renderer renderer; 75 protected RenderManager renderManager; 76 protected ViewPort viewPort; 77 protected ViewPort guiViewPort; 78 79 protected JmeContext context; 80 protected AppSettings settings; 81 protected Timer timer = new NanoTimer(); 82 protected Camera cam; 83 protected Listener listener; 84 85 protected boolean inputEnabled = true; 86 protected boolean pauseOnFocus = true; 87 protected float speed = 1f; 88 protected boolean paused = false; 89 protected MouseInput mouseInput; 90 protected KeyInput keyInput; 91 protected JoyInput joyInput; 92 protected TouchInput touchInput; 93 protected InputManager inputManager; 94 protected AppStateManager stateManager; 95 96 private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>(); 97 98 /** 99 * Create a new instance of <code>Application</code>. 100 */ Application()101 public Application(){ 102 initStateManager(); 103 } 104 105 /** 106 * Returns true if pause on lost focus is enabled, false otherwise. 107 * 108 * @return true if pause on lost focus is enabled 109 * 110 * @see #setPauseOnLostFocus(boolean) 111 */ isPauseOnLostFocus()112 public boolean isPauseOnLostFocus() { 113 return pauseOnFocus; 114 } 115 116 /** 117 * Enable or disable pause on lost focus. 118 * <p> 119 * By default, pause on lost focus is enabled. 120 * If enabled, the application will stop updating 121 * when it loses focus or becomes inactive (e.g. alt-tab). 122 * For online or real-time applications, this might not be preferable, 123 * so this feature should be set to disabled. For other applications, 124 * it is best to keep it on so that CPU usage is not used when 125 * not necessary. 126 * 127 * @param pauseOnLostFocus True to enable pause on lost focus, false 128 * otherwise. 129 */ setPauseOnLostFocus(boolean pauseOnLostFocus)130 public void setPauseOnLostFocus(boolean pauseOnLostFocus) { 131 this.pauseOnFocus = pauseOnLostFocus; 132 } 133 134 @Deprecated setAssetManager(AssetManager assetManager)135 public void setAssetManager(AssetManager assetManager){ 136 if (this.assetManager != null) 137 throw new IllegalStateException("Can only set asset manager" 138 + " before initialization."); 139 140 this.assetManager = assetManager; 141 } 142 initAssetManager()143 private void initAssetManager(){ 144 if (settings != null){ 145 String assetCfg = settings.getString("AssetConfigURL"); 146 if (assetCfg != null){ 147 URL url = null; 148 try { 149 url = new URL(assetCfg); 150 } catch (MalformedURLException ex) { 151 } 152 if (url == null) { 153 url = Application.class.getClassLoader().getResource(assetCfg); 154 if (url == null) { 155 logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg); 156 return; 157 } 158 } 159 assetManager = JmeSystem.newAssetManager(url); 160 } 161 } 162 if (assetManager == null){ 163 assetManager = JmeSystem.newAssetManager( 164 Thread.currentThread().getContextClassLoader() 165 .getResource("com/jme3/asset/Desktop.cfg")); 166 } 167 } 168 169 /** 170 * Set the display settings to define the display created. 171 * <p> 172 * Examples of display parameters include display pixel width and height, 173 * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency. 174 * If this method is called while the application is already running, then 175 * {@link #restart() } must be called to apply the settings to the display. 176 * 177 * @param settings The settings to set. 178 */ setSettings(AppSettings settings)179 public void setSettings(AppSettings settings){ 180 this.settings = settings; 181 if (context != null && settings.useInput() != inputEnabled){ 182 // may need to create or destroy input based 183 // on settings change 184 inputEnabled = !inputEnabled; 185 if (inputEnabled){ 186 initInput(); 187 }else{ 188 destroyInput(); 189 } 190 }else{ 191 inputEnabled = settings.useInput(); 192 } 193 } 194 195 /** 196 * Sets the Timer implementation that will be used for calculating 197 * frame times. By default, Application will use the Timer as returned 198 * by the current JmeContext implementation. 199 */ setTimer(Timer timer)200 public void setTimer(Timer timer){ 201 this.timer = timer; 202 203 if (timer != null) { 204 timer.reset(); 205 } 206 207 if (renderManager != null) { 208 renderManager.setTimer(timer); 209 } 210 } 211 getTimer()212 public Timer getTimer(){ 213 return timer; 214 } 215 initDisplay()216 private void initDisplay(){ 217 // aquire important objects 218 // from the context 219 settings = context.getSettings(); 220 221 // Only reset the timer if a user has not already provided one 222 if (timer == null) { 223 timer = context.getTimer(); 224 } 225 226 renderer = context.getRenderer(); 227 } 228 initAudio()229 private void initAudio(){ 230 if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){ 231 audioRenderer = JmeSystem.newAudioRenderer(settings); 232 audioRenderer.initialize(); 233 AudioContext.setAudioRenderer(audioRenderer); 234 235 listener = new Listener(); 236 audioRenderer.setListener(listener); 237 } 238 } 239 240 /** 241 * Creates the camera to use for rendering. Default values are perspective 242 * projection with 45° field of view, with near and far values 1 and 1000 243 * units respectively. 244 */ initCamera()245 private void initCamera(){ 246 cam = new Camera(settings.getWidth(), settings.getHeight()); 247 248 cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f); 249 cam.setLocation(new Vector3f(0f, 0f, 10f)); 250 cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); 251 252 renderManager = new RenderManager(renderer); 253 //Remy - 09/14/2010 setted the timer in the renderManager 254 renderManager.setTimer(timer); 255 viewPort = renderManager.createMainView("Default", cam); 256 viewPort.setClearFlags(true, true, true); 257 258 // Create a new cam for the gui 259 Camera guiCam = new Camera(settings.getWidth(), settings.getHeight()); 260 guiViewPort = renderManager.createPostView("Gui Default", guiCam); 261 guiViewPort.setClearFlags(false, false, false); 262 } 263 264 /** 265 * Initializes mouse and keyboard input. Also 266 * initializes joystick input if joysticks are enabled in the 267 * AppSettings. 268 */ initInput()269 private void initInput(){ 270 mouseInput = context.getMouseInput(); 271 if (mouseInput != null) 272 mouseInput.initialize(); 273 274 keyInput = context.getKeyInput(); 275 if (keyInput != null) 276 keyInput.initialize(); 277 278 touchInput = context.getTouchInput(); 279 if (touchInput != null) 280 touchInput.initialize(); 281 282 if (!settings.getBoolean("DisableJoysticks")){ 283 joyInput = context.getJoyInput(); 284 if (joyInput != null) 285 joyInput.initialize(); 286 } 287 288 inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput); 289 } 290 initStateManager()291 private void initStateManager(){ 292 stateManager = new AppStateManager(this); 293 294 // Always register a ResetStatsState to make sure 295 // that the stats are cleared every frame 296 stateManager.attach(new ResetStatsState()); 297 } 298 299 /** 300 * @return The {@link AssetManager asset manager} for this application. 301 */ getAssetManager()302 public AssetManager getAssetManager(){ 303 return assetManager; 304 } 305 306 /** 307 * @return the {@link InputManager input manager}. 308 */ getInputManager()309 public InputManager getInputManager(){ 310 return inputManager; 311 } 312 313 /** 314 * @return the {@link AppStateManager app state manager} 315 */ getStateManager()316 public AppStateManager getStateManager() { 317 return stateManager; 318 } 319 320 /** 321 * @return the {@link RenderManager render manager} 322 */ getRenderManager()323 public RenderManager getRenderManager() { 324 return renderManager; 325 } 326 327 /** 328 * @return The {@link Renderer renderer} for the application 329 */ getRenderer()330 public Renderer getRenderer(){ 331 return renderer; 332 } 333 334 /** 335 * @return The {@link AudioRenderer audio renderer} for the application 336 */ getAudioRenderer()337 public AudioRenderer getAudioRenderer() { 338 return audioRenderer; 339 } 340 341 /** 342 * @return The {@link Listener listener} object for audio 343 */ getListener()344 public Listener getListener() { 345 return listener; 346 } 347 348 /** 349 * @return The {@link JmeContext display context} for the application 350 */ getContext()351 public JmeContext getContext(){ 352 return context; 353 } 354 355 /** 356 * @return The {@link Camera camera} for the application 357 */ getCamera()358 public Camera getCamera(){ 359 return cam; 360 } 361 362 /** 363 * Starts the application in {@link Type#Display display} mode. 364 * 365 * @see #start(com.jme3.system.JmeContext.Type) 366 */ start()367 public void start(){ 368 start(JmeContext.Type.Display); 369 } 370 371 /** 372 * Starts the application. 373 * Creating a rendering context and executing 374 * the main loop in a separate thread. 375 */ start(JmeContext.Type contextType)376 public void start(JmeContext.Type contextType){ 377 if (context != null && context.isCreated()){ 378 logger.warning("start() called when application already created!"); 379 return; 380 } 381 382 if (settings == null){ 383 settings = new AppSettings(true); 384 } 385 386 logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); 387 context = JmeSystem.newContext(settings, contextType); 388 context.setSystemListener(this); 389 context.create(false); 390 } 391 392 /** 393 * Initializes the application's canvas for use. 394 * <p> 395 * After calling this method, cast the {@link #getContext() context} to 396 * {@link JmeCanvasContext}, 397 * then acquire the canvas with {@link JmeCanvasContext#getCanvas() } 398 * and attach it to an AWT/Swing Frame. 399 * The rendering thread will start when the canvas becomes visible on 400 * screen, however if you wish to start the context immediately you 401 * may call {@link #startCanvas() } to force the rendering thread 402 * to start. 403 * 404 * @see JmeCanvasContext 405 * @see Type#Canvas 406 */ createCanvas()407 public void createCanvas(){ 408 if (context != null && context.isCreated()){ 409 logger.warning("createCanvas() called when application already created!"); 410 return; 411 } 412 413 if (settings == null){ 414 settings = new AppSettings(true); 415 } 416 417 logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); 418 context = JmeSystem.newContext(settings, JmeContext.Type.Canvas); 419 context.setSystemListener(this); 420 } 421 422 /** 423 * Starts the rendering thread after createCanvas() has been called. 424 * <p> 425 * Same as calling startCanvas(false) 426 * 427 * @see #startCanvas(boolean) 428 */ startCanvas()429 public void startCanvas(){ 430 startCanvas(false); 431 } 432 433 /** 434 * Starts the rendering thread after createCanvas() has been called. 435 * <p> 436 * Calling this method is optional, the canvas will start automatically 437 * when it becomes visible. 438 * 439 * @param waitFor If true, the current thread will block until the 440 * rendering thread is running 441 */ startCanvas(boolean waitFor)442 public void startCanvas(boolean waitFor){ 443 context.create(waitFor); 444 } 445 446 /** 447 * Internal use only. 448 */ reshape(int w, int h)449 public void reshape(int w, int h){ 450 renderManager.notifyReshape(w, h); 451 } 452 453 /** 454 * Restarts the context, applying any changed settings. 455 * <p> 456 * Changes to the {@link AppSettings} of this Application are not 457 * applied immediately; calling this method forces the context 458 * to restart, applying the new settings. 459 */ restart()460 public void restart(){ 461 context.setSettings(settings); 462 context.restart(); 463 } 464 465 /** 466 * Requests the context to close, shutting down the main loop 467 * and making necessary cleanup operations. 468 * 469 * Same as calling stop(false) 470 * 471 * @see #stop(boolean) 472 */ stop()473 public void stop(){ 474 stop(false); 475 } 476 477 /** 478 * Requests the context to close, shutting down the main loop 479 * and making necessary cleanup operations. 480 * After the application has stopped, it cannot be used anymore. 481 */ stop(boolean waitFor)482 public void stop(boolean waitFor){ 483 logger.log(Level.FINE, "Closing application: {0}", getClass().getName()); 484 context.destroy(waitFor); 485 } 486 487 /** 488 * Do not call manually. 489 * Callback from ContextListener. 490 * <p> 491 * Initializes the <code>Application</code>, by creating a display and 492 * default camera. If display settings are not specified, a default 493 * 640x480 display is created. Default values are used for the camera; 494 * perspective projection with 45° field of view, with near 495 * and far values 1 and 1000 units respectively. 496 */ initialize()497 public void initialize(){ 498 if (assetManager == null){ 499 initAssetManager(); 500 } 501 502 initDisplay(); 503 initCamera(); 504 505 if (inputEnabled){ 506 initInput(); 507 } 508 initAudio(); 509 510 // update timer so that the next delta is not too large 511 // timer.update(); 512 timer.reset(); 513 514 // user code here.. 515 } 516 517 /** 518 * Internal use only. 519 */ handleError(String errMsg, Throwable t)520 public void handleError(String errMsg, Throwable t){ 521 logger.log(Level.SEVERE, errMsg, t); 522 // user should add additional code to handle the error. 523 stop(); // stop the application 524 } 525 526 /** 527 * Internal use only. 528 */ gainFocus()529 public void gainFocus(){ 530 if (pauseOnFocus) { 531 paused = false; 532 context.setAutoFlushFrames(true); 533 if (inputManager != null) { 534 inputManager.reset(); 535 } 536 } 537 } 538 539 /** 540 * Internal use only. 541 */ loseFocus()542 public void loseFocus(){ 543 if (pauseOnFocus){ 544 paused = true; 545 context.setAutoFlushFrames(false); 546 } 547 } 548 549 /** 550 * Internal use only. 551 */ requestClose(boolean esc)552 public void requestClose(boolean esc){ 553 context.destroy(false); 554 } 555 556 /** 557 * Enqueues a task/callable object to execute in the jME3 558 * rendering thread. 559 * <p> 560 * Callables are executed right at the beginning of the main loop. 561 * They are executed even if the application is currently paused 562 * or out of focus. 563 */ enqueue(Callable<V> callable)564 public <V> Future<V> enqueue(Callable<V> callable) { 565 AppTask<V> task = new AppTask<V>(callable); 566 taskQueue.add(task); 567 return task; 568 } 569 570 /** 571 * Do not call manually. 572 * Callback from ContextListener. 573 */ update()574 public void update(){ 575 // Make sure the audio renderer is available to callables 576 AudioContext.setAudioRenderer(audioRenderer); 577 578 AppTask<?> task = taskQueue.poll(); 579 toploop: do { 580 if (task == null) break; 581 while (task.isCancelled()) { 582 task = taskQueue.poll(); 583 if (task == null) break toploop; 584 } 585 task.invoke(); 586 } while (((task = taskQueue.poll()) != null)); 587 588 /* I think the above is really just doing this: 589 AppTask<?> task; 590 while( (task = taskQueue.poll()) != null ) { 591 if (!task.isCancelled()) { 592 task.invoke(); 593 } 594 } 595 //...but it's hard to say for sure. It's so twisted 596 //up that I don't trust my eyes. -pspeed 597 */ 598 599 if (speed == 0 || paused) 600 return; 601 602 timer.update(); 603 604 if (inputEnabled){ 605 inputManager.update(timer.getTimePerFrame()); 606 } 607 608 if (audioRenderer != null){ 609 audioRenderer.update(timer.getTimePerFrame()); 610 } 611 612 // user code here.. 613 } 614 destroyInput()615 protected void destroyInput(){ 616 if (mouseInput != null) 617 mouseInput.destroy(); 618 619 if (keyInput != null) 620 keyInput.destroy(); 621 622 if (joyInput != null) 623 joyInput.destroy(); 624 625 if (touchInput != null) 626 touchInput.destroy(); 627 628 inputManager = null; 629 } 630 631 /** 632 * Do not call manually. 633 * Callback from ContextListener. 634 */ destroy()635 public void destroy(){ 636 stateManager.cleanup(); 637 638 destroyInput(); 639 if (audioRenderer != null) 640 audioRenderer.cleanup(); 641 642 timer.reset(); 643 } 644 645 /** 646 * @return The GUI viewport. Which is used for the on screen 647 * statistics and FPS. 648 */ getGuiViewPort()649 public ViewPort getGuiViewPort() { 650 return guiViewPort; 651 } 652 getViewPort()653 public ViewPort getViewPort() { 654 return viewPort; 655 } 656 657 } 658