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 */
32package com.jme3.app;
33
34import com.jme3.app.state.AppState;
35import com.jme3.font.BitmapFont;
36import com.jme3.font.BitmapText;
37import com.jme3.input.FlyByCamera;
38import com.jme3.input.KeyInput;
39import com.jme3.input.controls.ActionListener;
40import com.jme3.input.controls.KeyTrigger;
41import com.jme3.math.Quaternion;
42import com.jme3.math.Vector3f;
43import com.jme3.renderer.RenderManager;
44import com.jme3.renderer.queue.RenderQueue.Bucket;
45import com.jme3.scene.Node;
46import com.jme3.scene.Spatial.CullHint;
47import com.jme3.system.AppSettings;
48import com.jme3.system.JmeContext.Type;
49import com.jme3.system.JmeSystem;
50import 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 */
67public 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
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
99    public SimpleApplication() {
100        this( new StatsAppState(), new FlyCamAppState(), new DebugKeysAppState() );
101    }
102
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
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     */
141    public FlyByCamera getFlyByCamera() {
142        return flyCam;
143    }
144
145    /**
146     * Retrieves guiNode
147     * @return guiNode Node object
148     *
149     */
150    public Node getGuiNode() {
151        return guiNode;
152    }
153
154    /**
155     * Retrieves rootNode
156     * @return rootNode Node object
157     *
158     */
159    public Node getRootNode() {
160        return rootNode;
161    }
162
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     */
172    public void setShowSettings(boolean showSettings) {
173        this.showSettings = showSettings;
174    }
175
176    @Override
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
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
251    public void setDisplayFps(boolean show) {
252        if (stateManager.getState(StatsAppState.class) != null) {
253            stateManager.getState(StatsAppState.class).setDisplayFps(show);
254        }
255    }
256
257    public void setDisplayStatView(boolean show) {
258        if (stateManager.getState(StatsAppState.class) != null) {
259            stateManager.getState(StatsAppState.class).setDisplayStatView(show);
260        }
261    }
262
263    public abstract void simpleInitApp();
264
265    public void simpleUpdate(float tpf) {
266    }
267
268    public void simpleRender(RenderManager rm) {
269    }
270}
271