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 34 import com.jme3.app.state.AppState; 35 import com.jme3.font.BitmapFont; 36 import com.jme3.font.BitmapText; 37 import com.jme3.input.FlyByCamera; 38 import com.jme3.input.KeyInput; 39 import com.jme3.input.controls.ActionListener; 40 import com.jme3.input.controls.KeyTrigger; 41 import com.jme3.math.Quaternion; 42 import com.jme3.math.Vector3f; 43 import com.jme3.renderer.RenderManager; 44 import com.jme3.renderer.queue.RenderQueue.Bucket; 45 import com.jme3.scene.Node; 46 import com.jme3.scene.Spatial.CullHint; 47 import com.jme3.system.AppSettings; 48 import com.jme3.system.JmeContext.Type; 49 import com.jme3.system.JmeSystem; 50 import com.jme3.util.BufferUtils; 51 52 /** 53 * <code>SimpleApplication</code> extends the {@link com.jme3.app.Application} 54 * class to provide default functionality like a first-person camera, 55 * and an accessible root node that is updated and rendered regularly. 56 * Additionally, <code>SimpleApplication</code> will display a statistics view 57 * using the {@link com.jme3.app.StatsView} class. It will display 58 * the current frames-per-second value on-screen in addition to the statistics. 59 * Several keys have special functionality in <code>SimpleApplication</code>:<br/> 60 * 61 * <table> 62 * <tr><td>Esc</td><td>- Close the application</td></tr> 63 * <tr><td>C</td><td>- Display the camera position and rotation in the console.</td></tr> 64 * <tr><td>M</td><td>- Display memory usage in the console.</td></tr> 65 * </table> 66 */ 67 public abstract class SimpleApplication extends Application { 68 69 public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit"; 70 public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS; 71 public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY; 72 public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats"; 73 74 protected Node rootNode = new Node("Root Node"); 75 protected Node guiNode = new Node("Gui Node"); 76 protected BitmapText fpsText; 77 protected BitmapFont guiFont; 78 protected FlyByCamera flyCam; 79 protected boolean showSettings = true; 80 private AppActionListener actionListener = new AppActionListener(); 81 82 private class AppActionListener implements ActionListener { 83 onAction(String name, boolean value, float tpf)84 public void onAction(String name, boolean value, float tpf) { 85 if (!value) { 86 return; 87 } 88 89 if (name.equals(INPUT_MAPPING_EXIT)) { 90 stop(); 91 }else if (name.equals(INPUT_MAPPING_HIDE_STATS)){ 92 if (stateManager.getState(StatsAppState.class) != null) { 93 stateManager.getState(StatsAppState.class).toggleStats(); 94 } 95 } 96 } 97 } 98 SimpleApplication()99 public SimpleApplication() { 100 this( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() ); 101 } 102 SimpleApplication( AppState... initialStates )103 public SimpleApplication( AppState... initialStates ) { 104 super(); 105 106 if (initialStates != null) { 107 for (AppState a : initialStates) { 108 if (a != null) { 109 stateManager.attach(a); 110 } 111 } 112 } 113 } 114 115 @Override start()116 public void start() { 117 // set some default settings in-case 118 // settings dialog is not shown 119 boolean loadSettings = false; 120 if (settings == null) { 121 setSettings(new AppSettings(true)); 122 loadSettings = true; 123 } 124 125 // show settings dialog 126 if (showSettings) { 127 if (!JmeSystem.showSettingsDialog(settings, loadSettings)) { 128 return; 129 } 130 } 131 //re-setting settings they can have been merged from the registry. 132 setSettings(settings); 133 super.start(); 134 } 135 136 /** 137 * Retrieves flyCam 138 * @return flyCam Camera object 139 * 140 */ getFlyByCamera()141 public FlyByCamera getFlyByCamera() { 142 return flyCam; 143 } 144 145 /** 146 * Retrieves guiNode 147 * @return guiNode Node object 148 * 149 */ getGuiNode()150 public Node getGuiNode() { 151 return guiNode; 152 } 153 154 /** 155 * Retrieves rootNode 156 * @return rootNode Node object 157 * 158 */ getRootNode()159 public Node getRootNode() { 160 return rootNode; 161 } 162 isShowSettings()163 public boolean isShowSettings() { 164 return showSettings; 165 } 166 167 /** 168 * Toggles settings window to display at start-up 169 * @param showSettings Sets true/false 170 * 171 */ setShowSettings(boolean showSettings)172 public void setShowSettings(boolean showSettings) { 173 this.showSettings = showSettings; 174 } 175 176 @Override initialize()177 public void initialize() { 178 super.initialize(); 179 180 // Several things rely on having this 181 guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); 182 183 guiNode.setQueueBucket(Bucket.Gui); 184 guiNode.setCullHint(CullHint.Never); 185 viewPort.attachScene(rootNode); 186 guiViewPort.attachScene(guiNode); 187 188 if (inputManager != null) { 189 190 // We have to special-case the FlyCamAppState because too 191 // many SimpleApplication subclasses expect it to exist in 192 // simpleInit(). But at least it only gets initialized if 193 // the app state is added. 194 if (stateManager.getState(FlyCamAppState.class) != null) { 195 flyCam = new FlyByCamera(cam); 196 flyCam.setMoveSpeed(1f); // odd to set this here but it did it before 197 stateManager.getState(FlyCamAppState.class).setCamera( flyCam ); 198 } 199 200 if (context.getType() == Type.Display) { 201 inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE)); 202 } 203 204 if (stateManager.getState(StatsAppState.class) != null) { 205 inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5)); 206 inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS); 207 } 208 209 inputManager.addListener(actionListener, INPUT_MAPPING_EXIT); 210 } 211 212 if (stateManager.getState(StatsAppState.class) != null) { 213 // Some of the tests rely on having access to fpsText 214 // for quick display. Maybe a different way would be better. 215 stateManager.getState(StatsAppState.class).setFont(guiFont); 216 fpsText = stateManager.getState(StatsAppState.class).getFpsText(); 217 } 218 219 // call user code 220 simpleInitApp(); 221 } 222 223 @Override update()224 public void update() { 225 super.update(); // makes sure to execute AppTasks 226 if (speed == 0 || paused) { 227 return; 228 } 229 230 float tpf = timer.getTimePerFrame() * speed; 231 232 // update states 233 stateManager.update(tpf); 234 235 // simple update and root node 236 simpleUpdate(tpf); 237 238 rootNode.updateLogicalState(tpf); 239 guiNode.updateLogicalState(tpf); 240 241 rootNode.updateGeometricState(); 242 guiNode.updateGeometricState(); 243 244 // render states 245 stateManager.render(renderManager); 246 renderManager.render(tpf, context.isRenderable()); 247 simpleRender(renderManager); 248 stateManager.postRender(); 249 } 250 setDisplayFps(boolean show)251 public void setDisplayFps(boolean show) { 252 if (stateManager.getState(StatsAppState.class) != null) { 253 stateManager.getState(StatsAppState.class).setDisplayFps(show); 254 } 255 } 256 setDisplayStatView(boolean show)257 public void setDisplayStatView(boolean show) { 258 if (stateManager.getState(StatsAppState.class) != null) { 259 stateManager.getState(StatsAppState.class).setDisplayStatView(show); 260 } 261 } 262 simpleInitApp()263 public abstract void simpleInitApp(); 264 simpleUpdate(float tpf)265 public void simpleUpdate(float tpf) { 266 } 267 simpleRender(RenderManager rm)268 public void simpleRender(RenderManager rm) { 269 } 270 } 271