Application.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  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. package com.jme3.app;
  33. import com.jme3.app.state.AppStateManager;
  34. import com.jme3.input.JoyInput;
  35. import com.jme3.input.KeyInput;
  36. import com.jme3.input.MouseInput;
  37. import com.jme3.input.TouchInput;
  38. import com.jme3.math.Vector3f;
  39. import com.jme3.renderer.Camera;
  40. import com.jme3.renderer.Renderer;
  41. import com.jme3.asset.AssetManager;
  42. import com.jme3.audio.AudioContext;
  43. import com.jme3.audio.AudioRenderer;
  44. import com.jme3.audio.Listener;
  45. import com.jme3.input.InputManager;
  46. import com.jme3.renderer.RenderManager;
  47. import com.jme3.renderer.ViewPort;
  48. import com.jme3.system.AppSettings;
  49. import com.jme3.system.JmeCanvasContext;
  50. import com.jme3.system.JmeContext;
  51. import java.net.MalformedURLException;
  52. import java.net.URL;
  53. import java.util.concurrent.Callable;
  54. import java.util.concurrent.ConcurrentLinkedQueue;
  55. import java.util.concurrent.Future;
  56. import java.util.logging.Level;
  57. import java.util.logging.Logger;
  58. import com.jme3.system.JmeContext.Type;
  59. import com.jme3.system.JmeSystem;
  60. import com.jme3.system.NanoTimer;
  61. import com.jme3.system.SystemListener;
  62. import com.jme3.system.Timer;
  63. /**
  64. * The <code>Application</code> class represents an instance of a
  65. * real-time 3D rendering jME application.
  66. *
  67. * An <code>Application</code> provides all the tools that are commonly used in jME3
  68. * applications.
  69. *
  70. * jME3 applications should extend this class and call start() to begin the
  71. * application.
  72. *
  73. */
  74. public class Application implements SystemListener {
  75. private static final Logger logger = Logger.getLogger(Application.class.getName());
  76. protected AssetManager assetManager;
  77. protected AudioRenderer audioRenderer;
  78. protected Renderer renderer;
  79. protected RenderManager renderManager;
  80. protected ViewPort viewPort;
  81. protected ViewPort guiViewPort;
  82. protected JmeContext context;
  83. protected AppSettings settings;
  84. protected Timer timer = new NanoTimer();
  85. protected Camera cam;
  86. protected Listener listener;
  87. protected boolean inputEnabled = true;
  88. protected boolean pauseOnFocus = true;
  89. protected float speed = 1f;
  90. protected boolean paused = false;
  91. protected MouseInput mouseInput;
  92. protected KeyInput keyInput;
  93. protected JoyInput joyInput;
  94. protected TouchInput touchInput;
  95. protected InputManager inputManager;
  96. protected AppStateManager stateManager;
  97. private final ConcurrentLinkedQueue<AppTask<?>> taskQueue = new ConcurrentLinkedQueue<AppTask<?>>();
  98. /**
  99. * Create a new instance of <code>Application</code>.
  100. */
  101. public Application(){
  102. }
  103. /**
  104. * Returns true if pause on lost focus is enabled, false otherwise.
  105. *
  106. * @return true if pause on lost focus is enabled
  107. *
  108. * @see #setPauseOnLostFocus(boolean)
  109. */
  110. public boolean isPauseOnLostFocus() {
  111. return pauseOnFocus;
  112. }
  113. /**
  114. * Enable or disable pause on lost focus.
  115. * <p>
  116. * By default, pause on lost focus is enabled.
  117. * If enabled, the application will stop updating
  118. * when it loses focus or becomes inactive (e.g. alt-tab).
  119. * For online or real-time applications, this might not be preferable,
  120. * so this feature should be set to disabled. For other applications,
  121. * it is best to keep it on so that CPU usage is not used when
  122. * not necessary.
  123. *
  124. * @param pauseOnLostFocus True to enable pause on lost focus, false
  125. * otherwise.
  126. */
  127. public void setPauseOnLostFocus(boolean pauseOnLostFocus) {
  128. this.pauseOnFocus = pauseOnLostFocus;
  129. }
  130. @Deprecated
  131. public void setAssetManager(AssetManager assetManager){
  132. if (this.assetManager != null)
  133. throw new IllegalStateException("Can only set asset manager"
  134. + " before initialization.");
  135. this.assetManager = assetManager;
  136. }
  137. private void initAssetManager(){
  138. if (settings != null){
  139. String assetCfg = settings.getString("AssetConfigURL");
  140. if (assetCfg != null){
  141. URL url = null;
  142. try {
  143. url = new URL(assetCfg);
  144. } catch (MalformedURLException ex) {
  145. }
  146. if (url == null) {
  147. url = Application.class.getClassLoader().getResource(assetCfg);
  148. if (url == null) {
  149. logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg);
  150. return;
  151. }
  152. }
  153. assetManager = JmeSystem.newAssetManager(url);
  154. }
  155. }
  156. if (assetManager == null){
  157. assetManager = JmeSystem.newAssetManager(
  158. Thread.currentThread().getContextClassLoader()
  159. .getResource("com/jme3/asset/Desktop.cfg"));
  160. }
  161. }
  162. /**
  163. * Set the display settings to define the display created.
  164. * <p>
  165. * Examples of display parameters include display pixel width and height,
  166. * color bit depth, z-buffer bits, anti-aliasing samples, and update frequency.
  167. * If this method is called while the application is already running, then
  168. * {@link #restart() } must be called to apply the settings to the display.
  169. *
  170. * @param settings The settings to set.
  171. */
  172. public void setSettings(AppSettings settings){
  173. this.settings = settings;
  174. if (context != null && settings.useInput() != inputEnabled){
  175. // may need to create or destroy input based
  176. // on settings change
  177. inputEnabled = !inputEnabled;
  178. if (inputEnabled){
  179. initInput();
  180. }else{
  181. destroyInput();
  182. }
  183. }else{
  184. inputEnabled = settings.useInput();
  185. }
  186. }
  187. /**
  188. * Sets the Timer implementation that will be used for calculating
  189. * frame times. By default, Application will use the Timer as returned
  190. * by the current JmeContext implementation.
  191. */
  192. public void setTimer(Timer timer){
  193. this.timer = timer;
  194. if (timer != null) {
  195. timer.reset();
  196. }
  197. if (renderManager != null) {
  198. renderManager.setTimer(timer);
  199. }
  200. }
  201. private void initDisplay(){
  202. // aquire important objects
  203. // from the context
  204. settings = context.getSettings();
  205. // Only reset the timer if a user has not already provided one
  206. if (timer == null) {
  207. timer = context.getTimer();
  208. }
  209. renderer = context.getRenderer();
  210. }
  211. private void initAudio(){
  212. if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){
  213. audioRenderer = JmeSystem.newAudioRenderer(settings);
  214. audioRenderer.initialize();
  215. AudioContext.setAudioRenderer(audioRenderer);
  216. listener = new Listener();
  217. audioRenderer.setListener(listener);
  218. }
  219. }
  220. /**
  221. * Creates the camera to use for rendering. Default values are perspective
  222. * projection with 45° field of view, with near and far values 1 and 1000
  223. * units respectively.
  224. */
  225. private void initCamera(){
  226. cam = new Camera(settings.getWidth(), settings.getHeight());
  227. cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f);
  228. cam.setLocation(new Vector3f(0f, 0f, 10f));
  229. cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
  230. renderManager = new RenderManager(renderer);
  231. //Remy - 09/14/2010 setted the timer in the renderManager
  232. renderManager.setTimer(timer);
  233. viewPort = renderManager.createMainView("Default", cam);
  234. viewPort.setClearFlags(true, true, true);
  235. // Create a new cam for the gui
  236. Camera guiCam = new Camera(settings.getWidth(), settings.getHeight());
  237. guiViewPort = renderManager.createPostView("Gui Default", guiCam);
  238. guiViewPort.setClearFlags(false, false, false);
  239. }
  240. /**
  241. * Initializes mouse and keyboard input. Also
  242. * initializes joystick input if joysticks are enabled in the
  243. * AppSettings.
  244. */
  245. private void initInput(){
  246. mouseInput = context.getMouseInput();
  247. if (mouseInput != null)
  248. mouseInput.initialize();
  249. keyInput = context.getKeyInput();
  250. if (keyInput != null)
  251. keyInput.initialize();
  252. touchInput = context.getTouchInput();
  253. if (touchInput != null)
  254. touchInput.initialize();
  255. if (!settings.getBoolean("DisableJoysticks")){
  256. joyInput = context.getJoyInput();
  257. if (joyInput != null)
  258. joyInput.initialize();
  259. }
  260. inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput);
  261. }
  262. private void initStateManager(){
  263. stateManager = new AppStateManager(this);
  264. }
  265. /**
  266. * @return The {@link AssetManager asset manager} for this application.
  267. */
  268. public AssetManager getAssetManager(){
  269. return assetManager;
  270. }
  271. /**
  272. * @return the {@link InputManager input manager}.
  273. */
  274. public InputManager getInputManager(){
  275. return inputManager;
  276. }
  277. /**
  278. * @return the {@link AppStateManager app state manager}
  279. */
  280. public AppStateManager getStateManager() {
  281. return stateManager;
  282. }
  283. /**
  284. * @return the {@link RenderManager render manager}
  285. */
  286. public RenderManager getRenderManager() {
  287. return renderManager;
  288. }
  289. /**
  290. * @return The {@link Renderer renderer} for the application
  291. */
  292. public Renderer getRenderer(){
  293. return renderer;
  294. }
  295. /**
  296. * @return The {@link AudioRenderer audio renderer} for the application
  297. */
  298. public AudioRenderer getAudioRenderer() {
  299. return audioRenderer;
  300. }
  301. /**
  302. * @return The {@link Listener listener} object for audio
  303. */
  304. public Listener getListener() {
  305. return listener;
  306. }
  307. /**
  308. * @return The {@link JmeContext display context} for the application
  309. */
  310. public JmeContext getContext(){
  311. return context;
  312. }
  313. /**
  314. * @return The {@link Camera camera} for the application
  315. */
  316. public Camera getCamera(){
  317. return cam;
  318. }
  319. /**
  320. * Starts the application in {@link Type#Display display} mode.
  321. *
  322. * @see #start(com.jme3.system.JmeContext.Type)
  323. */
  324. public void start(){
  325. start(JmeContext.Type.Display);
  326. }
  327. /**
  328. * Starts the application.
  329. * Creating a rendering context and executing
  330. * the main loop in a separate thread.
  331. */
  332. public void start(JmeContext.Type contextType){
  333. if (context != null && context.isCreated()){
  334. logger.warning("start() called when application already created!");
  335. return;
  336. }
  337. if (settings == null){
  338. settings = new AppSettings(true);
  339. }
  340. logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
  341. context = JmeSystem.newContext(settings, contextType);
  342. context.setSystemListener(this);
  343. context.create(false);
  344. }
  345. /**
  346. * Initializes the application's canvas for use.
  347. * <p>
  348. * After calling this method, cast the {@link #getContext() context} to
  349. * {@link JmeCanvasContext},
  350. * then acquire the canvas with {@link JmeCanvasContext#getCanvas() }
  351. * and attach it to an AWT/Swing Frame.
  352. * The rendering thread will start when the canvas becomes visible on
  353. * screen, however if you wish to start the context immediately you
  354. * may call {@link #startCanvas() } to force the rendering thread
  355. * to start.
  356. *
  357. * @see JmeCanvasContext
  358. * @see Type#Canvas
  359. */
  360. public void createCanvas(){
  361. if (context != null && context.isCreated()){
  362. logger.warning("createCanvas() called when application already created!");
  363. return;
  364. }
  365. if (settings == null){
  366. settings = new AppSettings(true);
  367. }
  368. logger.log(Level.FINE, "Starting application: {0}", getClass().getName());
  369. context = JmeSystem.newContext(settings, JmeContext.Type.Canvas);
  370. context.setSystemListener(this);
  371. }
  372. /**
  373. * Starts the rendering thread after createCanvas() has been called.
  374. * <p>
  375. * Same as calling startCanvas(false)
  376. *
  377. * @see #startCanvas(boolean)
  378. */
  379. public void startCanvas(){
  380. startCanvas(false);
  381. }
  382. /**
  383. * Starts the rendering thread after createCanvas() has been called.
  384. * <p>
  385. * Calling this method is optional, the canvas will start automatically
  386. * when it becomes visible.
  387. *
  388. * @param waitFor If true, the current thread will block until the
  389. * rendering thread is running
  390. */
  391. public void startCanvas(boolean waitFor){
  392. context.create(waitFor);
  393. }
  394. /**
  395. * Internal use only.
  396. */
  397. public void reshape(int w, int h){
  398. renderManager.notifyReshape(w, h);
  399. }
  400. /**
  401. * Restarts the context, applying any changed settings.
  402. * <p>
  403. * Changes to the {@link AppSettings} of this Application are not
  404. * applied immediately; calling this method forces the context
  405. * to restart, applying the new settings.
  406. */
  407. public void restart(){
  408. context.setSettings(settings);
  409. context.restart();
  410. }
  411. /**
  412. * Requests the context to close, shutting down the main loop
  413. * and making necessary cleanup operations.
  414. *
  415. * Same as calling stop(false)
  416. *
  417. * @see #stop(boolean)
  418. */
  419. public void stop(){
  420. stop(false);
  421. }
  422. /**
  423. * Requests the context to close, shutting down the main loop
  424. * and making necessary cleanup operations.
  425. * After the application has stopped, it cannot be used anymore.
  426. */
  427. public void stop(boolean waitFor){
  428. logger.log(Level.FINE, "Closing application: {0}", getClass().getName());
  429. context.destroy(waitFor);
  430. }
  431. /**
  432. * Do not call manually.
  433. * Callback from ContextListener.
  434. * <p>
  435. * Initializes the <code>Application</code>, by creating a display and
  436. * default camera. If display settings are not specified, a default
  437. * 640x480 display is created. Default values are used for the camera;
  438. * perspective projection with 45° field of view, with near
  439. * and far values 1 and 1000 units respectively.
  440. */
  441. public void initialize(){
  442. if (assetManager == null){
  443. initAssetManager();
  444. }
  445. initDisplay();
  446. initCamera();
  447. if (inputEnabled){
  448. initInput();
  449. }
  450. initAudio();
  451. initStateManager();
  452. // update timer so that the next delta is not too large
  453. // timer.update();
  454. timer.reset();
  455. // user code here..
  456. }
  457. /**
  458. * Internal use only.
  459. */
  460. public void handleError(String errMsg, Throwable t){
  461. logger.log(Level.SEVERE, errMsg, t);
  462. // user should add additional code to handle the error.
  463. stop(); // stop the application
  464. }
  465. /**
  466. * Internal use only.
  467. */
  468. public void gainFocus(){
  469. if (pauseOnFocus) {
  470. paused = false;
  471. context.setAutoFlushFrames(true);
  472. if (inputManager != null) {
  473. inputManager.reset();
  474. }
  475. }
  476. }
  477. /**
  478. * Internal use only.
  479. */
  480. public void loseFocus(){
  481. if (pauseOnFocus){
  482. paused = true;
  483. context.setAutoFlushFrames(false);
  484. }
  485. }
  486. /**
  487. * Internal use only.
  488. */
  489. public void requestClose(boolean esc){
  490. context.destroy(false);
  491. }
  492. /**
  493. * Enqueues a task/callable object to execute in the jME3
  494. * rendering thread.
  495. * <p>
  496. * Callables are executed right at the beginning of the main loop.
  497. * They are executed even if the application is currently paused
  498. * or out of focus.
  499. */
  500. public <V> Future<V> enqueue(Callable<V> callable) {
  501. AppTask<V> task = new AppTask<V>(callable);
  502. taskQueue.add(task);
  503. return task;
  504. }
  505. /**
  506. * Do not call manually.
  507. * Callback from ContextListener.
  508. */
  509. public void update(){
  510. // Make sure the audio renderer is available to callables
  511. AudioContext.setAudioRenderer(audioRenderer);
  512. AppTask<?> task = taskQueue.poll();
  513. toploop: do {
  514. if (task == null) break;
  515. while (task.isCancelled()) {
  516. task = taskQueue.poll();
  517. if (task == null) break toploop;
  518. }
  519. task.invoke();
  520. } while (((task = taskQueue.poll()) != null));
  521. if (speed == 0 || paused)
  522. return;
  523. timer.update();
  524. if (inputEnabled){
  525. inputManager.update(timer.getTimePerFrame());
  526. }
  527. if (audioRenderer != null){
  528. audioRenderer.update(timer.getTimePerFrame());
  529. }
  530. // user code here..
  531. }
  532. protected void destroyInput(){
  533. if (mouseInput != null)
  534. mouseInput.destroy();
  535. if (keyInput != null)
  536. keyInput.destroy();
  537. if (joyInput != null)
  538. joyInput.destroy();
  539. if (touchInput != null)
  540. touchInput.destroy();
  541. inputManager = null;
  542. }
  543. /**
  544. * Do not call manually.
  545. * Callback from ContextListener.
  546. */
  547. public void destroy(){
  548. stateManager.cleanup();
  549. destroyInput();
  550. if (audioRenderer != null)
  551. audioRenderer.cleanup();
  552. timer.reset();
  553. }
  554. /**
  555. * @return The GUI viewport. Which is used for the on screen
  556. * statistics and FPS.
  557. */
  558. public ViewPort getGuiViewPort() {
  559. return guiViewPort;
  560. }
  561. public ViewPort getViewPort() {
  562. return viewPort;
  563. }
  564. }