OSDN Git Service

Change build system ant to sbt.
[mikumikustudio/MikuMikuStudio.git] / engine / src / core / com / jme3 / app / Application.java
1 /*\r
2  * Copyright (c) 2009-2010 jMonkeyEngine\r
3  * All rights reserved.\r
4  *\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
7  * met:\r
8  *\r
9  * * Redistributions of source code must retain the above copyright\r
10  *   notice, this list of conditions and the following disclaimer.\r
11  *\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
15  *\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
19  *\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
31  */\r
32 \r
33 package com.jme3.app;\r
34 \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
63 \r
64 /**\r
65  * The <code>Application</code> class represents an instance of a\r
66  * real-time 3D rendering jME application.\r
67  *\r
68  * An <code>Application</code> provides all the tools that are commonly used in jME3\r
69  * applications.\r
70  *\r
71  * jME3 applications should extend this class and call start() to begin the\r
72  * application.\r
73  * \r
74  */\r
75 public class Application implements SystemListener {\r
76 \r
77     private static final Logger logger = Logger.getLogger(Application.class.getName());\r
78 \r
79     protected AssetManager assetManager;\r
80     \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
86 \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
92 \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
103 \r
104     private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();\r
105 \r
106     /**\r
107      * Create a new instance of <code>Application</code>.\r
108      */\r
109     public Application(){\r
110     }\r
111 \r
112     /**\r
113      * Returns true if pause on lost focus is enabled, false otherwise.\r
114      * \r
115      * @return true if pause on lost focus is enabled\r
116      * \r
117      * @see #setPauseOnLostFocus(boolean) \r
118      */\r
119     public boolean isPauseOnLostFocus() {\r
120         return pauseOnFocus;\r
121     }\r
122 \r
123     /**\r
124      * Enable or disable pause on lost focus.\r
125      * <p>\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
132      * not necessary. \r
133      * \r
134      * @param pauseOnLostFocus True to enable pause on lost focus, false\r
135      * otherwise.\r
136      */\r
137     public void setPauseOnLostFocus(boolean pauseOnLostFocus) {\r
138         this.pauseOnFocus = pauseOnLostFocus;\r
139     }\r
140 \r
141     @Deprecated\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
146 \r
147         this.assetManager = assetManager;\r
148     }\r
149 \r
150     private void initAssetManager(){\r
151         if (settings != null){\r
152             String assetCfg = settings.getString("AssetConfigURL");\r
153             if (assetCfg != null){\r
154                 URL url = null;\r
155                 try {\r
156                     url = new URL(assetCfg);\r
157                 } catch (MalformedURLException ex) {\r
158                 }\r
159                 if (url == null) {\r
160                     url = Application.class.getClassLoader().getResource(assetCfg);\r
161                     if (url == null) {\r
162                         logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);\r
163                         return;\r
164                     }\r
165                 }\r
166                 assetManager = JmeSystem.newAssetManager(url);\r
167             }\r
168         }\r
169         if (assetManager == null){\r
170             assetManager = JmeSystem.newAssetManager(\r
171                     Thread.currentThread().getContextClassLoader()\r
172                     .getResource("com/jme3/asset/Desktop.cfg"));\r
173         }\r
174     }\r
175 \r
176     /**\r
177      * Set the display settings to define the display created.\r
178      * <p>\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
183      *\r
184      * @param settings The settings to set.\r
185      */\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
192             if (inputEnabled){\r
193                 initInput();\r
194             }else{\r
195                 destroyInput();\r
196             }\r
197         }else{\r
198             inputEnabled = settings.useInput();\r
199         }\r
200     }\r
201 \r
202     /**\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
206      */\r
207     public void setTimer(Timer timer){\r
208         this.timer = timer;\r
209         \r
210         if (timer != null) {\r
211             timer.reset();\r
212         }\r
213         \r
214         if (renderManager != null) {\r
215             renderManager.setTimer(timer);\r
216         }\r
217     } \r
218 \r
219     private void initDisplay(){\r
220         // aquire important objects\r
221         // from the context\r
222         settings = context.getSettings();\r
223  \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
227         }\r
228        \r
229         renderer = context.getRenderer();\r
230     }\r
231 \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
237 \r
238             listener = new Listener();\r
239             audioRenderer.setListener(listener);\r
240         }\r
241     }\r
242 \r
243     /**\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
247      */\r
248     private void initCamera(){\r
249         cam = new Camera(settings.getWidth(), settings.getHeight());\r
250 \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
254 \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
260 \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
265     }\r
266 \r
267     /**\r
268      * Initializes mouse and keyboard input. Also\r
269      * initializes joystick input if joysticks are enabled in the\r
270      * AppSettings.\r
271      */\r
272     private void initInput(){\r
273         mouseInput = context.getMouseInput();\r
274         if (mouseInput != null)\r
275             mouseInput.initialize();\r
276 \r
277         keyInput = context.getKeyInput();\r
278         if (keyInput != null)\r
279             keyInput.initialize();\r
280         \r
281         touchInput = context.getTouchInput();\r
282         if (touchInput != null)\r
283             touchInput.initialize();\r
284 \r
285         if (!settings.getBoolean("DisableJoysticks")){\r
286             joyInput = context.getJoyInput();\r
287             if (joyInput != null)\r
288                 joyInput.initialize();\r
289         }\r
290 \r
291         inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);\r
292     }\r
293 \r
294     private void initStateManager(){\r
295         stateManager = new AppStateManager(this);\r
296     }\r
297 \r
298     /**\r
299      * @return The {@link AssetManager asset manager} for this application.\r
300      */\r
301     public AssetManager getAssetManager(){\r
302         return assetManager;\r
303     }\r
304 \r
305     /**\r
306      * @return the {@link InputManager input manager}.\r
307      */\r
308     public InputManager getInputManager(){\r
309         return inputManager;\r
310     }\r
311 \r
312     /**\r
313      * @return the {@link AppStateManager app state manager}\r
314      */\r
315     public AppStateManager getStateManager() {\r
316         return stateManager;\r
317     }\r
318 \r
319     /**\r
320      * @return the {@link RenderManager render manager}\r
321      */\r
322     public RenderManager getRenderManager() {\r
323         return renderManager;\r
324     }\r
325 \r
326     /**\r
327      * @return The {@link Renderer renderer} for the application\r
328      */\r
329     public Renderer getRenderer(){\r
330         return renderer;\r
331     }\r
332 \r
333     /**\r
334      * @return The {@link AudioRenderer audio renderer} for the application\r
335      */\r
336     public AudioRenderer getAudioRenderer() {\r
337         return audioRenderer;\r
338     }\r
339 \r
340     /**\r
341      * @return The {@link Listener listener} object for audio\r
342      */\r
343     public Listener getListener() {\r
344         return listener;\r
345     }\r
346 \r
347     /**\r
348      * @return The {@link JmeContext display context} for the application\r
349      */\r
350     public JmeContext getContext(){\r
351         return context;\r
352     }\r
353 \r
354     /**\r
355      * @return The {@link Camera camera} for the application\r
356      */\r
357     public Camera getCamera(){\r
358         return cam;\r
359     }\r
360 \r
361     /**\r
362      * Starts the application in {@link Type#Display display} mode.\r
363      * \r
364      * @see #start(com.jme3.system.JmeContext.Type) \r
365      */\r
366     public void start(){\r
367         start(JmeContext.Type.Display);\r
368     }\r
369 \r
370     /**\r
371      * Starts the application. \r
372      * Creating a rendering context and executing\r
373      * the main loop in a separate thread.\r
374      */\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
378             return;\r
379         }\r
380 \r
381         if (settings == null){\r
382             settings = new AppSettings(true);\r
383         }\r
384         \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
389     }\r
390 \r
391     /**\r
392      * Initializes the application's canvas for use.\r
393      * <p>\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
401      * to start. \r
402      * \r
403      * @see JmeCanvasContext\r
404      * @see Type#Canvas\r
405      */\r
406     public void createCanvas(){\r
407         if (context != null && context.isCreated()){\r
408             logger.warning("createCanvas() called when application already created!");\r
409             return;\r
410         }\r
411 \r
412         if (settings == null){\r
413             settings = new AppSettings(true);\r
414         }\r
415 \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
419     }\r
420 \r
421     /**\r
422      * Starts the rendering thread after createCanvas() has been called.\r
423      * <p>\r
424      * Same as calling startCanvas(false)\r
425      * \r
426      * @see #startCanvas(boolean) \r
427      */\r
428     public void startCanvas(){\r
429         startCanvas(false);\r
430     }\r
431 \r
432     /**\r
433      * Starts the rendering thread after createCanvas() has been called.\r
434      * <p>\r
435      * Calling this method is optional, the canvas will start automatically\r
436      * when it becomes visible.\r
437      * \r
438      * @param waitFor If true, the current thread will block until the \r
439      * rendering thread is running\r
440      */\r
441     public void startCanvas(boolean waitFor){\r
442         context.create(waitFor);\r
443     }\r
444 \r
445     /**\r
446      * Internal use only. \r
447      */\r
448     public void reshape(int w, int h){\r
449         renderManager.notifyReshape(w, h);\r
450     }\r
451 \r
452     /**\r
453      * Restarts the context, applying any changed settings.\r
454      * <p>\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
458      */\r
459     public void restart(){\r
460         context.setSettings(settings);\r
461         context.restart();\r
462     }\r
463 \r
464     /**\r
465      * Requests the context to close, shutting down the main loop\r
466      * and making necessary cleanup operations.\r
467      * \r
468      * Same as calling stop(false)\r
469      * \r
470      * @see #stop(boolean) \r
471      */\r
472     public void stop(){\r
473         stop(false);\r
474     }\r
475 \r
476     /**\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
480      */\r
481     public void stop(boolean waitFor){\r
482         logger.log(Level.FINE, "Closing application: {0}", getClass().getName());\r
483         context.destroy(waitFor);\r
484     }\r
485 \r
486     /**\r
487      * Do not call manually.\r
488      * Callback from ContextListener.\r
489      * <p>\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
495      */\r
496     public void initialize(){\r
497         if (assetManager == null){\r
498             initAssetManager();\r
499         }\r
500 \r
501         initDisplay();\r
502         initCamera();\r
503         \r
504         if (inputEnabled){\r
505             initInput();\r
506         }\r
507         initAudio();\r
508         initStateManager();\r
509 \r
510         // update timer so that the next delta is not too large\r
511 //        timer.update();\r
512         timer.reset();\r
513 \r
514         // user code here..\r
515     }\r
516 \r
517     /**\r
518      * Internal use only.\r
519      */\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
524     }\r
525 \r
526     /**\r
527      * Internal use only.\r
528      */\r
529     public void gainFocus(){\r
530         if (pauseOnFocus) {\r
531             paused = false;\r
532             context.setAutoFlushFrames(true);\r
533             if (inputManager != null) {\r
534                 inputManager.reset();\r
535             }\r
536         }\r
537     }\r
538 \r
539     /**\r
540      * Internal use only.\r
541      */\r
542     public void loseFocus(){\r
543         if (pauseOnFocus){\r
544             paused = true;\r
545             context.setAutoFlushFrames(false);\r
546         }\r
547     }\r
548 \r
549     /**\r
550      * Internal use only.\r
551      */\r
552     public void requestClose(boolean esc){\r
553         context.destroy(false);\r
554     }\r
555 \r
556     /**\r
557      * Enqueues a task/callable object to execute in the jME3\r
558      * rendering thread. \r
559      * <p>\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
562      * or out of focus.\r
563      */\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
567         return task;\r
568     }\r
569 \r
570     /**\r
571      * Do not call manually.\r
572      * Callback from ContextListener.\r
573      */\r
574     public void update(){\r
575         // Make sure the audio renderer is available to callables\r
576         AudioContext.setAudioRenderer(audioRenderer);\r
577         \r
578         AppTask<?> task = taskQueue.poll();\r
579         toploop: do {\r
580             if (task == null) break;\r
581             while (task.isCancelled()) {\r
582                 task = taskQueue.poll();\r
583                 if (task == null) break toploop;\r
584             }\r
585             task.invoke();\r
586         } while (((task = taskQueue.poll()) != null));\r
587     \r
588         if (speed == 0 || paused)\r
589             return;\r
590 \r
591         timer.update();\r
592 \r
593         if (inputEnabled){\r
594             inputManager.update(timer.getTimePerFrame());\r
595         }\r
596 \r
597         if (audioRenderer != null){\r
598             audioRenderer.update(timer.getTimePerFrame());\r
599         }\r
600 \r
601         // user code here..\r
602     }\r
603 \r
604     protected void destroyInput(){\r
605         if (mouseInput != null)\r
606             mouseInput.destroy();\r
607 \r
608         if (keyInput != null)\r
609             keyInput.destroy();\r
610 \r
611         if (joyInput != null)\r
612             joyInput.destroy();\r
613         \r
614         if (touchInput != null)\r
615             touchInput.destroy();        \r
616 \r
617         inputManager = null;\r
618     }\r
619 \r
620     /**\r
621      * Do not call manually.\r
622      * Callback from ContextListener.\r
623      */\r
624     public void destroy(){\r
625         stateManager.cleanup();\r
626         \r
627         destroyInput();\r
628         if (audioRenderer != null)\r
629             audioRenderer.cleanup();\r
630         \r
631         timer.reset();\r
632     }\r
633 \r
634     /**\r
635      * @return The GUI viewport. Which is used for the on screen\r
636      * statistics and FPS.\r
637      */\r
638     public ViewPort getGuiViewPort() {\r
639         return guiViewPort;\r
640     }\r
641 \r
642     public ViewPort getViewPort() {\r
643         return viewPort;\r
644     }\r
645 \r
646 }\r