• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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