/*********************************************************************** * mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * ***********************************************************************/ package org.mt4j; import java.awt.Dimension; import java.awt.Toolkit; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.List; import java.util.Properties; import javax.media.opengl.GL; import javax.swing.ImageIcon; import org.apache.log4j.ConsoleAppender; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.apache.log4j.SimpleLayout; import org.mt4j.input.InputManager; import org.mt4j.input.inputData.AbstractCursorInputEvt; import org.mt4j.input.inputData.ActiveCursorPool; import org.mt4j.input.inputData.InputCursor; import org.mt4j.input.inputProcessors.globalProcessors.AbstractGlobalInputProcessor; import org.mt4j.input.inputSources.AbstractInputSource; import org.mt4j.sceneManagement.ISceneChangeListener; import org.mt4j.sceneManagement.Iscene; import org.mt4j.sceneManagement.SceneChangeEvent; import org.mt4j.sceneManagement.transition.ITransition; import org.mt4j.util.MT4jSettings; import org.mt4j.util.SettingsMenu; import org.mt4j.util.animation.AnimationManager; import org.mt4j.util.math.Tools3D; import org.mt4j.util.opengl.GLFBO; import processing.core.PApplet; /** * Use this class to create a new multitouch application. * <br>The best way to create your application would be to extend this class and * put the <code>main</code> method into that class. * In the <code>main</code> method call the <code>initialize()</code> method. * Then override the <code>startUp()</code> method which is called * automatically after the initialize method. The <code>startUp()</code> method can be used to * create your scenes (extend the <code>AbstractScene</code> class) and add them to * the application by calling <code>addScene</code> method. * * <p>Internally, the main method of processings PApplet class is called with the class name * of the extended PApplet class as an argument. The PApplet class then instantiates the given * class and calls its setup() and then repeatedly its run() method. * * @author Christopher Ruff */ public abstract class MTApplication extends PApplet { /** The Constant logger. */ private static final Logger logger = Logger.getLogger(MTApplication.class.getName()); static{ // logger.setLevel(Level.ERROR); // logger.setLevel(Level.WARN); // logger.setLevel(Level.DEBUG); logger.setLevel(Level.INFO); SimpleLayout l = new SimpleLayout(); ConsoleAppender ca = new ConsoleAppender(l); logger.addAppender(ca); } public static String CUSTOM_OPENGL_GRAPHICS = "org.mt4j.util.opengl.CustomPGraphicsOpenGL"; //PApplet.OPENGL /** The Constant serialVersionUID. */ private static final long serialVersionUID = 1L; /** The scene change locked. */ private boolean sceneChangeLocked; // private static MTApplication mtApp = null; /** The scene list. */ private List<Iscene> sceneList; /** The current scene. */ private Iscene currentScene; /** The animation mgr. */ private AnimationManager animMgr; /** The time last frame. */ private long timeLastFrame ; /** The already run. */ private boolean alreadyRun; /** The v sync. */ private boolean vSync = false; /** The input manager. */ private InputManager inputManager; /** The scene changed listeners. */ private List<ISceneChangeListener> sceneChangedListeners; /** The invoke later actions. */ private Deque<Runnable> invokeLaterActions; /** The scene stack. */ private ArrayDeque<Iscene> sceneStack; private Thread renderThread; public static String separator = "/"; public static char separatorChar = '/'; private static boolean settingsLoadedFromFile = false; //cant initialize in constructor, need it before that! private ImageIcon mt4jIcon; // private static boolean fullscreen; /* public static void main(String[] args){ // MTApplication app = new MTApplication(); PApplet.main(new String[] { // "--present", // "--exclusive", "--bgcolor=#000000", "--hide-stop", "org.mt4j.MTApplication" } ); } @Override public void setup(){ size(800,600, OPENGL); //TODO REMOVE logger.debug("Setup"); System.out.println("Setup called"); smooth(); hint(ENABLE_OPENGL_2X_SMOOTH ); smooth(); noSmooth(); background(0); GL gl = Tools3D.getGL(this); // gl.glEnable(GL.GL_MULTISAMPLE); // gl.glEnable(GL.GL_MULTISAMPLE_EXT); } @Override public void draw(){ // background(255); fill(250,0,0,255); stroke(250,0,0,255); line(0,10, 280,20); GL gl = Tools3D.beginGL(this); // GL gl = ((PGraphicsOpenGL)this.g).beginGL(); // gl.glEnable(GL.GL_LINE_SMOOTH ); gl.glDisable(GL.GL_LINE_SMOOTH ); // gl.glHint(GL.GL_LINE_SMOOTH_HINT, GL.GL_NICEST); // Enable Blending gl.glEnable(GL.GL_BLEND); // Specifies pixel arithmetic gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); gl.glLineWidth(1); gl.glColor4d(0.0, 0.0, 0.0, 1); gl.glBegin(GL.GL_LINE_STRIP); gl.glVertex3d(0, 20, 0); gl.glVertex3d(280, 30, 0); gl.glEnd(); gl.glBegin(GL.GL_LINE_STRIP); gl.glVertex3d(0, 20, 0); gl.glVertex3d(711, 230, 0); gl.glVertex3d(200, 300, 0); gl.glVertex3d(100, 330, 0); gl.glEnd(); // ((PGraphicsOpenGL)this.g).endGL(); Tools3D.endGL(this); if (this.mousePressed){ fill(150); rect(mouseX, mouseY, 10,10); } } */ /* //TODO test to make window undecorated - seems to mess up some textures (maybe because opengl re-initialization) //put frame.setLocation(-1600, 0); at the end of setup() to position the frame public void init(){ // to make a frame not displayable, you can // use frame.removeNotify() frame.removeNotify(); frame.setUndecorated(true); // addNotify, here i am not sure if you have // to add notify again. frame.addNotify(); super.init(); } */ /** * Dont instiatiate this class directly! * It gets instantiated by the PApplet class via * java reflection. */ public MTApplication(){ sceneList = new ArrayList<Iscene>(); currentScene = null; animMgr = AnimationManager.getInstance(); alreadyRun = false; sceneChangedListeners = new ArrayList<ISceneChangeListener>(); invokeLaterActions = new ArrayDeque<Runnable>(); sceneStack = new ArrayDeque<Iscene>(); sceneChangeLocked = false; } /** * Initializes the processings settings. * Call this method in your main method prior to anything else! */ public static void initialize(){ initialize(new CurrentClassGetter().getClassName()); } public static void initialize(boolean showSettingsMenu){ initialize(new CurrentClassGetter().getClassName(), showSettingsMenu); } public static void initialize(String classToInstantiate){ initialize(classToInstantiate, false); } /** * Initializes the processings settings. * Call this method in your main method prior to anything else! * We have to provide the fully qualified name to the class that * we are calling this from. (Should be our MTAplication extended class) * This is needed because processing will use the reflection api to instantiate * an instance of the MTApplication class. * <br>E.g.: <code>initialize("myPackage.myMainClass");</code> * * @param classToInstantiate the class to instantiate * @param showSettingsMenu show settings menu */ public static void initialize(String classToInstantiate, boolean showSettingsMenu){ logger.debug(classToInstantiate + " is the class instatiated by PApplet class."); //FIXME TEST if (showSettingsMenu){ settingsLoadedFromFile = true; SettingsMenu menu = new SettingsMenu(classToInstantiate); menu.setVisible(true); }else{ getSettingsFromFile(); // Launch processing PApplet main() function if (MT4jSettings.getInstance().isFullscreen()){ if (MT4jSettings.getInstance().isFullscreenExclusive()){ PApplet.main(new String[] { "--display=" + MT4jSettings.getInstance().getDisplay(), "--present", "--exclusive", "--bgcolor=#000000", "--hide-stop", classToInstantiate} ); }else{ PApplet.main(new String[] { "--display=" + MT4jSettings.getInstance().getDisplay(), "--present", "--bgcolor=#000000", "--hide-stop", classToInstantiate} ); } }else{ PApplet.main(new String[] { "--display=" + MT4jSettings.getInstance().getDisplay(), classToInstantiate }); } } } private static void getSettingsFromFile(){ //Load some properties from Settings.txt file Properties properties = new Properties(); try { try { FileInputStream fi = new FileInputStream(MT4jSettings.getInstance().getDefaultSettingsPath() + "Settings.txt"); properties.load(fi); } catch (FileNotFoundException e) { logger.debug("Couldnt load Settings.txt from the File system. Trying to load it as a resource.."); InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("Settings.txt"); if (in != null){ properties.load(in); }else{ logger.debug("Couldnt load Settings.txt as a resource. Using defaults."); throw new FileNotFoundException("Couldnt load Settings.txt as a resource"); } } MT4jSettings.fullscreen = Boolean.parseBoolean(properties.getProperty("Fullscreen", new Boolean(MT4jSettings.getInstance().isFullscreen()).toString()).trim()); //Use java's fullscreen exclusive mode (real fullscreen) or just use an undecorated window at fullscreen size MT4jSettings.getInstance().fullscreenExclusive = Boolean.parseBoolean(properties.getProperty("FullscreenExclusive", new Boolean(MT4jSettings.getInstance().isFullscreenExclusive()).toString()).trim()); //Which display to use for fullscreen MT4jSettings.getInstance().display = Integer.parseInt(properties.getProperty("Display", String.valueOf(MT4jSettings.getInstance().getDisplay())).trim()); //FIXME at fullscreen really use the screen dimension? -> we need to set the native resoultion ourselves! //so we can have a lower fullscreen resolution than the screen dimensions if (!MT4jSettings.getInstance().isFullscreen()){ MT4jSettings.getInstance().windowWidth = Integer.parseInt(properties.getProperty("DisplayWidth", String.valueOf(MT4jSettings.getInstance().getWindowWidth())).trim()); MT4jSettings.getInstance().windowHeight = Integer.parseInt(properties.getProperty("DisplayHeight", String.valueOf(MT4jSettings.getInstance().getWindowHeight())).trim()); }else{ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); MT4jSettings.getInstance().windowWidth = screenSize.width; MT4jSettings.getInstance().windowHeight = screenSize.height; } MT4jSettings.getInstance().maxFrameRate = Integer.parseInt(properties.getProperty("MaximumFrameRate", String.valueOf(MT4jSettings.getInstance().getMaxFrameRate())).trim()); MT4jSettings.getInstance().renderer = Integer.parseInt(properties.getProperty("Renderer", String.valueOf(MT4jSettings.getInstance().getRendererMode())).trim()); MT4jSettings.getInstance().numSamples = Integer.parseInt(properties.getProperty("OpenGLAntialiasing", String.valueOf(MT4jSettings.getInstance().getNumSamples())).trim()); MT4jSettings.getInstance().vSync = Boolean.parseBoolean(properties.getProperty("Vertical_sync", new Boolean(MT4jSettings.getInstance().isVerticalSynchronization()).toString()).trim()); //Set frametitle String frameTitle = properties.getProperty("Frametitle", MT4jSettings.getInstance().getFrameTitle().trim()); MT4jSettings.getInstance().frameTitle = frameTitle; } catch (Exception e) { logger.error("Error while loading Settings.txt. Using defaults."); } settingsLoadedFromFile = true; } /** * *********************************************************** * Processings setup. this is called once when the applet is started * Used to define some initial settings * **********************************************************. */ @Override public void setup(){ //TOGGLES ALWAYS ON TOP MODE //this.frame.setAlwaysOnTop(true); logger.debug("-> setup called"); //Check if OS 32/64 Bit String bit = System.getProperty("sun.arch.data.model"); logger.info("Platform: \"" + System.getProperty("os.name") + "\" -> Version: \"" + System.getProperty("os.version") + "\" -> JVM Bit: \"" + bit + "\""); MT4jSettings.getInstance().architecture = bit.contains("64")? MT4jSettings.ARCHITECTURE_64_BIT : MT4jSettings.ARCHITECTURE_32_BIT; if (!settingsLoadedFromFile){ getSettingsFromFile(); } // //Load some properties from Settings.txt file // Properties properties = new Properties(); // try { // FileInputStream fi = new FileInputStream(MT4jSettings.getInstance().getDefaultSettingsPath() + "Settings.txt"); // if (fi != null){ // properties.load(fi); // }else{ // InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("Settings.txt"); // if (in != null){ // properties.load(in); // }else{ // properties.load(getClass().getResourceAsStream("Settings.txt")); // } // } // // //FIXME at fullscreen really use the screen dimension? -> we need to set the native resoultion ourselves! // //so we can have a lower fullscreen resolution than the screen dimensions // if (!MT4jSettings.getInstance().isFullscreen()){ // MT4jSettings.getInstance().setScreenWidth(Integer.parseInt(properties.getProperty("DisplayWidth", "1024"))); // MT4jSettings.getInstance().setScreenHeight(Integer.parseInt(properties.getProperty("DisplayHeight", "768"))); // } // MT4jSettings.getInstance().setMaxFrameRate(Integer.parseInt(properties.getProperty("MaximumFrameRate", "60"))); // MT4jSettings.getInstance().setRendererMode(Integer.parseInt(properties.getProperty("Renderer", new Integer(MT4jSettings.P3D_MODE).toString()))); // MT4jSettings.getInstance().setNumSamples((Integer.parseInt(properties.getProperty("OpenGLAntialiasing", new Integer(0).toString())))); // // vSync = Boolean.parseBoolean(properties.getProperty("Vertical_sync", "false")); // //Set frametitle // String frameTitle = properties.getProperty("Frametitle", "MT-Application"); // MT4jSettings.getInstance().setFrameTitle(frameTitle); // } catch (Exception e) { // logger.error("Error while loading Settings.txt file. Using defaults. (" + e.getMessage() + ")"); // } // Applet size - size() must be the first command in setup() method if (MT4jSettings.getInstance().getRendererMode() == MT4jSettings.OPENGL_MODE) this.size(MT4jSettings.getInstance().getWindowWidth(), MT4jSettings.getInstance().getWindowHeight(), MTApplication.CUSTOM_OPENGL_GRAPHICS); else if (MT4jSettings.getInstance().getRendererMode() == MT4jSettings.P3D_MODE) this.size(MT4jSettings.getInstance().getWindowWidth(), MT4jSettings.getInstance().getWindowHeight(), PApplet.P3D); /* //Processing Bug? seems to always use 2 samples if (MT4jSettings.getInstance().getNumSamples() <= 0){ hint(DISABLE_OPENGL_2X_SMOOTH); }else if (MT4jSettings.getInstance().getNumSamples() == 2){ //Nothing to set, Processing default anyway }else if (MT4jSettings.getInstance().getNumSamples() == 4){ hint(DISABLE_OPENGL_2X_SMOOTH); hint(ENABLE_OPENGL_4X_SMOOTH); } */ // pContext.hint( PApplet.ENABLE_OPENGL_4X_SMOOTH ); // ENABLES OPENGL EXTRA SMOOTHING -> DOESENT GET CONSISTENT RESULTS ON ALL MACHINES! DISABLE WHEN PROBLEMS OCCUR! //hint(ENABLE_DEPTH_SORT); // Enable primitive z-sorting of triangles and lines in P3D and OPENGL. This can slow performance considerably, and the algorithm is not yet perfect. //hint(DISABLE_ERROR_REPORT); // Speeds up the OPENGL renderer setting by not checking for errors while running. //hint(ENABLE_ACCURATE_TEXTURES); //Enables better texture accuracy for the P3D renderer. This option will do a better job of dealing with textures in perspective. // Save this applets rendering thread for reference this.renderThread = Thread.currentThread(); //System.out.println("Current Thread: "+ Thread.currentThread()); // Set frame icon image try { //Set the window frame's title frame.setTitle(MT4jSettings.getInstance().getFrameTitle()); // this.mt4jIcon = new ImageIcon(MT4jSettings.getInstance().getDefaultImagesPath() + // "MT4j.gif"); this.mt4jIcon = new ImageIcon(Thread.currentThread().getContextClassLoader().getResource(MT4jSettings.getInstance().getDefaultImagesPath() + "MT4j.gif")); this.frame.setIconImage(mt4jIcon.getImage()); }catch (Exception e){ e.printStackTrace(); } logger.info("MT4j window dimensions: \"" + MT4jSettings.getInstance().getWindowWidth() + " X " + MT4jSettings.getInstance().getWindowHeight() + "\""); // //Set background color // pContext.background(MT4jSettings.getInstance().getBackgroundClearColor()); background(150); //Set the framerate frameRate(MT4jSettings.getInstance().getMaxFrameRate()); logger.info("Maximum framerate: \"" + MT4jSettings.getInstance().getMaxFrameRate() + "\""); //FIXME TODO add in settings.txt? hint(MTApplication.DISABLE_OPENGL_ERROR_REPORT); MT4jSettings.getInstance().programStartTime = System.currentTimeMillis(); //Apply some opengl settings like V-Syncing or multi-Sampling this.applyOpenGLStartSettings(); //Create a new inputsourcePool this.setInputManager(new InputManager(this)); //Call startup at the end of setup(). Should be overridden in extending classes this.startUp(); /* * Resizable Window test * Problems: * - all textures, shaders etc get destroyed because a new gl context is created * - cursor coordiantes are calculated wrong? we prolly have to update Papplet width/height frame.setResizable(true); frame.addComponentListener(new ComponentAdapter() { public void componentResized(ComponentEvent e) { if(e.getSource() == frame) { frame.setSize(frame.getWidth(), minHeight); } } } ); */ } /** * Apply open gl start settings. */ private void applyOpenGLStartSettings(){ //TODO pa.smooth() / pa.noSmooth() ver�ndert auch line_smooth! //f�r test ob multisampling lines ohne Line_smooth okay rendered m�ssen //sicherheitshalber auch die pa.smoot() etc abgefangen werden und line_smooth immer disabled sein! //TODO check line drawing and abstractvisible at stencil in this context (line_smooth) //TODO // - if multisampling enabled dont do line smoothing at all // - OR: disable multisampling each time before doing line_smoothing! (better but expensive?) // -> info: disabling multisampling isnt possible at runtime.. // - or disable mutisample before drawing with line_smooth! //TOOD dont use lines to smooth some objects then (fonts, etc) if (MT4jSettings.getInstance().isOpenGlMode() ){ GL gl = Tools3D.getGL(this); logger.info("OpenGL Version: \"" + gl.glGetString(GL.GL_VERSION) + "\"" + " - Vendor: \"" + gl.glGetString(GL.GL_VENDOR) + "\"" + " - Renderer: \"" + gl.glGetString(GL.GL_RENDERER) + "\""); // logger.info("Shading language version: \"" + gl.glGetString(GL.GL_SHADING_LANGUAGE_VERSION) + "\""); logger.info("Non power of two texture sizes allowed: \"" + Tools3D.supportsNonPowerOfTwoTexture(this) + "\""); logger.info("OpenGL Framebuffer Object Extension available: \"" + GLFBO.isSupported(this) + "\""); //Set VSyncing on -> to avoid tearing //-> check if gfx card settings allow apps to set it! //-> Use with caution! only use with fps rate == monitor Hz! //and fps never drop below Hz! -> else choppy! //-> only works with opengl! Tools3D.setVSyncing(this, vSync); logger.info("Vertical Sync enabled: \"" + vSync + "\""); if ( MT4jSettings.getInstance().isMultiSampling()){ gl.glEnable(GL.GL_MULTISAMPLE); // gl.glDisable(GL.GL_MULTISAMPLE); logger.info("OpenGL multi-sampling enabled."); } gl.glEnable(GL.GL_LINE_SMOOTH); // gl.glDisable(GL.GL_LINE_SMOOTH); } } public void setOpenGLErrorReportingEnabled(boolean reportErros){ if (reportErros){ hint(MTApplication.ENABLE_OPENGL_ERROR_REPORT); }else{ hint(MTApplication.DISABLE_OPENGL_ERROR_REPORT); } } /** * ******************************************************************************************** * Processings draw() gets called repeatedly by processings PApplet Class - unless noloop() is called * ********************************************************************************************. */ @Override public void draw(){ this.runApplication(); } /** * Is called at the end of the setup() method. * <br>Override this method in your extended MTApplication class! */ public abstract void startUp(); /** * Main run loop. * <li>Updates the time passed since the last time drawn. * <li>Updates any animations with the new time delta. * <li>Updates and draws the current scene. * <li>Updates and draws the current scene transitions. */ private void runApplication(){ // /* //Use nanoTime if (!alreadyRun){ alreadyRun = true; timeLastFrame = System.nanoTime(); } long nanos = System.nanoTime(); long timeDelta = (nanos - timeLastFrame) / 1000000L; timeLastFrame = nanos; // */ /* //Use currentTimeMillis if (!alreadyRun){ alreadyRun = true; timeLastFrame = System.currentTimeMillis(); } long millis = System.currentTimeMillis(); long timeDelta = millis - timeLastFrame; timeLastFrame = millis; */ // System.out.println("TimeDelta: " + timeDelta); //Run invoke later actions synchronized (invokeLaterActions) { while (!invokeLaterActions.isEmpty()){ invokeLaterActions.pollFirst().run(); } } //Update animation manager animMgr.update(timeDelta); // /* //Handle scene transitions if (this.pendingTransition != null){ //Run the transition this.pendingTransition.transition.drawAndUpdate(this.g, timeDelta); if (this.pendingTransition.transition.isFinished()){ this.pendingTransition.transition.shutDown(); this.doSceneChange(this.getCurrentScene(), this.pendingTransition.nextScene); this.pendingTransition = null; } }else{ //Draw the current scene Iscene theCurrentScene = this.getCurrentScene(); if (theCurrentScene != null){ theCurrentScene.drawAndUpdate(this.g, timeDelta); } } // */ /* //Update scene sceneMgr.updateCurrentScene(timeDelta); //Draw scene sceneMgr.drawCurrentScene(); */ } /** * Checks if is render thread is current. * * @return true, if is render thread current */ public boolean isRenderThreadCurrent(){ return Thread.currentThread().equals(renderThread); } /** * Invokes the specified runnable at the beginning the next rendering loop in the rendering thread. * This is especially useful for executing opengl commands from another thread - which would lead to errors * if not synchronized with the rendering thread. * * @param runnable the runnable */ public void invokeLater(Runnable runnable){ synchronized (invokeLaterActions) { invokeLaterActions.addLast(runnable); } } /** * Checks which scene is on top of the scene stack at the moment. * If no scene has been pushed on the stack, null is returned. * * @return the iscene */ public Iscene peekScene(){ return sceneStack.peek(); } public int getSceneStackCount(){ return sceneStack.size(); } /** * Pushes the current scene on the scene stack. */ public void pushScene(){ if (getCurrentScene() == null){ logger.debug("Scene stack is empty! No scene to put on the stack!"); }else{ logger.debug("Putting scene: " + getCurrentScene().getName() + " on the stack."); sceneStack.offerFirst(getCurrentScene()); } } /** * Pops the scene thats currently ontop of the scene stack and changes back to it. * If the stack is empty no error is thrown and no scene change will happen. */ public boolean popScene(){ // Iscene stackScene = sceneStack.pollFirst(); Iscene stackScene = sceneStack.peek(); if (stackScene != null){ logger.debug("Popping scene: " + stackScene.getName() + " back from the stack."); boolean changed = this.changeScene(stackScene); if (changed){ sceneStack.pollFirst(); return true; }else{ return false; } }else{ logger.warn("Scene stack is empty! No scene to pop from the stack!"); return false; } } private boolean inDoSceneChange = false; private TransitionInfo pendingTransition; /** * The Class TransitionInfo. Holding info about a scene change transition. * @author Christopher Ruff */ private class TransitionInfo{ ITransition transition; Iscene lastScene; Iscene nextScene; boolean destroyLastSceneAfterTransition = false; public TransitionInfo(ITransition transition, Iscene lastScene, Iscene nextScene){ this.transition = transition; this.lastScene = lastScene; this.nextScene = nextScene; } } /** * Initiates the scene change. Checks if the old scene has a transition * and sets it to be used in the main loop. * * @param oldScene the old scene * @param newScene the new scene */ private boolean initiateSceneChange(Iscene oldScene, Iscene newScene){ //FIXME TEST! if (oldScene.equals(newScene)){ logger.error("Trying to change from and to the same scene."); return false; } //Lock scene changes to only 1 at a time. At sending the bridge events during the //scene change, it could occur that a scene change could be triggered again which we prevent if (!sceneChangeLocked){ sceneChangeLocked = true; Iscene lastScene = this.getCurrentScene(); //Remove pending animations // //FIXME problemes, if new animations are defined in a scenes constructor, they get removed here.. //AnimationManager.getInstance().clear(); //Flush events so that enqueued input ended get sent to the last scene //(Problem: they have been removed from active cursor pool already so they dont //appear there and no ended and started evts are sent to the scenes! //IF input started or updated should be flushed with this they should appear in active //cursor list after that and be sended the right events //- maybe only flush input_ended? for (AbstractInputSource abstractInputSource : getInputManager().getInputSources()) { abstractInputSource.flushEvents(); } //Check which cursors are still active and clone their last evt as INPUT_ENDED //so the scene can complete its state (i.e. buttons are be released etc) this.sendEndedEvents(lastScene); //Disable the last scene's global input processors this.getInputManager().disableGlobalInputProcessors(lastScene); // /* if (lastScene.getTransition() != null){ ITransition t = lastScene.getTransition(); this.pendingTransition = new TransitionInfo(t, lastScene, newScene); t.init(); t.setup(lastScene, newScene); return true; }else{ return this.doSceneChange(lastScene, newScene); } // */ //doSceneChange(oldScene, newScene); }else{ logger.debug("Couldnt change scene -> Change is locked from another scene change."); return false; } } /** * Does the scene change after the transition (if existing) is completed. * @param oldScene the old scene * @param newScene the new scene */ private boolean doSceneChange(Iscene oldScene, Iscene newScene){ if (sceneChangeLocked && !inDoSceneChange){ inDoSceneChange = true; //Maybe show loading progress for newScenne.Init first? oldScene.shutDown(); //Initialize new Scene newScene.init(); //Enable input Processors previously registered with that scene this.getInputManager().enableGlobalInputProcessors(newScene); //Check which cursors are active and clone their last evt as INPUT_DETECTED //so the scene doesent get INPUT_UPDATED without the start events this.sendStartedEvents(newScene); //Set new current scene this.currentScene = newScene; //FIXME TEST -> Make it possible to destroy scenes after a transition //(During a transition the old scene cant be removed or destroyed because //its still the current scene!) if (pendingTransition != null){ if (pendingTransition.destroyLastSceneAfterTransition){ logger.debug("Destroying scene: " + pendingTransition.lastScene.getName() + " after the transition."); pendingTransition.lastScene.destroy(); } } if (!this.sceneChangedListeners.isEmpty()){ this.fireSceneChangeEvent(new SceneChangeEvent(this, oldScene, newScene)); } logger.debug("Scene changed from: '" + oldScene + "' to: '" + newScene + "'"); sceneChangeLocked = false; inDoSceneChange = false; return true; }else{ return false; } } /** * Changes the scene to the specified scene. * <p>NOTE: This is not threadsafe while using OpenGL mode. If in openGL mode make, * sure to call this only from the same thread. If running in a different thread, * execute the scene change using the <code>invokeLater(Runnable runnable)</code> method * of the MTApplication instance! * <p>NOTE: If the scene is not already added to the application by invoking <code>addScene()</code>, the scene * is automatically added to the mtapplication. * * @param newScene the new scene */ public synchronized boolean changeScene(Iscene newScene){ if (!this.sceneList.contains(newScene)){ this.addScene(newScene); } return this.initiateSceneChange(this.getCurrentScene(), newScene); } /** * Checks which cursors are active during the scene change and * sends input_ended events of the active cursors to last scene's global input processors * so actions in the last scene can be completed correctly. * This means that one cursor can have more than one input_ended and input_started event * in its event list! * * @param lastScene the last scene * @param newScene the new scene */ private void sendEndedEvents(Iscene lastScene){ logger.debug("Sending INPUT_ENDED events to the last scene, Active motions: " + ActiveCursorPool.getInstance().getActiveCursorCount()); InputCursor[] activeCursors = ActiveCursorPool.getInstance().getActiveCursors(); for (int i = 0; i < activeCursors.length; i++) { InputCursor inputCursor = activeCursors[i]; if (inputCursor.getCurrentEvent() != null){ AbstractCursorInputEvt lastEvt = inputCursor.getCurrentEvent(); if (lastEvt.getId() != AbstractCursorInputEvt.INPUT_ENDED){ try { AbstractCursorInputEvt endedEvt = (AbstractCursorInputEvt) lastEvt.clone(); endedEvt.setId(AbstractCursorInputEvt.INPUT_ENDED); endedEvt.preFire(); this.sendEvtToSceneProcessors(lastScene, endedEvt); logger.debug("Sending INPUT_ENDED evt to scene: " + lastScene.getName() + " Cursor: " + endedEvt.getCursor()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } } } /** * Checks which cursors are active during the scene change and * sends input_started to the new scene's global input processors so actions in the * last scene can be completed correctly. * This means that one cursor can have more than one input_ended and input_started event * in its event list! * * @param lastScene the last scene * @param newScene the new scene */ private void sendStartedEvents(Iscene newScene){ logger.debug("Sending INPUT_DETECTED events to the new scene, Active motions: " + ActiveCursorPool.getInstance().getActiveCursorCount()); InputCursor[] activeCursors = ActiveCursorPool.getInstance().getActiveCursors(); for (int i = 0; i < activeCursors.length; i++) { InputCursor inputCursor = activeCursors[i]; if (inputCursor.getCurrentEvent() != null){ //PROBLEM: if in lastscene last event in cursor was input_started enqueued //but not added to cursor yet, //shall we send it again in new scene? -> will input_started be sent twice? //- what if input started was enqueued during transition and not sent to any scene AbstractCursorInputEvt lastEvt = inputCursor.getCurrentEvent(); /* if (//lastEvt.getId() != AbstractCursorInputEvt.INPUT_DETECTED true ){ */ try { AbstractCursorInputEvt startedEvt = (AbstractCursorInputEvt) lastEvt.clone(); startedEvt.setId(AbstractCursorInputEvt.INPUT_DETECTED); startedEvt.preFire(); this.sendEvtToSceneProcessors(newScene, startedEvt); logger.debug("Sending INPUT_DETECTED evt to scene: " + newScene.getName() + " Cursor: " + startedEvt.getCursor()); } catch (CloneNotSupportedException e) { e.printStackTrace(); } // } } } } /** * Send evt to scene processors. * * @param scene the scene * @param evtToFire the evt to fire */ private void sendEvtToSceneProcessors(Iscene scene, AbstractCursorInputEvt evtToFire){ AbstractGlobalInputProcessor[] sceneInputProcessors = this.getInputManager().getGlobalInputProcessors(scene); for (int i = 0; i < sceneInputProcessors.length; i++) { AbstractGlobalInputProcessor a = sceneInputProcessors[i]; //Hack, because processInputEvt() is disabled at this moment! -> not anymore.. // a.processInputEvtImpl(evtToFire); a.processInputEvent(evtToFire); } } /** * Gets the currently active scene. * * @return the current scene */ public Iscene getCurrentScene(){ return currentScene; } /* public void drawCurrentScene(){ getCurrentScene().draw(); } public void updateCurrentScene(long timeDelta){ getCurrentScene().update(timeDelta); } */ /** * Adds the scene to the list of scenes. * Also changes to that scene if it is the first one to be added. * * @param scene the scene */ public void addScene(Iscene scene){ if (this.getSceneCount() == 0){ scene.init(); this.currentScene = scene; this.getInputManager().enableGlobalInputProcessors(scene); this.fireSceneChangeEvent(new SceneChangeEvent(this, this.currentScene, this.currentScene)); } if (!sceneList.contains(scene)) sceneList.add(scene); } /** * Adds all scenes. * * @param scenes the scenes */ public void addAll(Iscene[] scenes){ // if (this.getSceneCount() == 0 && scenes[0] != null){ // this.currentScene = scenes[0]; // } for (int i = 0; i < scenes.length; i++) { Iscene scene = scenes[i]; // sceneList.add(scene); this.addScene(scene); } } /** * Removes the scene from the list of scenes. Fails if the scene is the currently active scene. * If the scene isnt going to be used anymore, calling the scene's destroy() method is the better choice * than the removeScene method alone. * * @param scene the scene */ public boolean removeScene(Iscene scene){ if (sceneList.contains(scene)){ if (scene.equals(this.currentScene)){ logger.warn("Cant remove the scene if it is the currently active scene! (" + scene + ")"); return false; }else{ sceneList.remove(scene); return true; } } else{ return false; } // return true; } /** * Destroy scene after transition. Workaround so that if a scene's destroy() method is called * but the scene is in a transition (cant be removed then) we call destroy on the scene after * the transition. * Only has an impact if there is a pending transition with the specified scene as the last scene. * * @param scene the scene */ public void destroySceneAfterTransition(Iscene scene){ if (pendingTransition != null && pendingTransition.lastScene.equals(scene)){ pendingTransition.destroyLastSceneAfterTransition = true; } } /** * Gets the registered scenes. * * @return the scenes */ public Iscene[] getScenes(){ return ((Iscene[])sceneList.toArray(new Iscene[sceneList.size()]) ); } /** * Gets the scene by name. * * @param name the name * * @return the scene */ public Iscene getScene(String name){ Iscene returnScene = null; for(Iscene scene : sceneList){ if (scene.getName().equals(name)) returnScene = scene; } return returnScene; } /** * Gets the scene count. * * @return the scene count */ public int getSceneCount(){ return sceneList.size(); } /** * Gets the input manager. * * @return the input manager */ public InputManager getInputManager() { return inputManager; } /** * Sets the input manager. * * @param inputManager the new input manager */ public void setInputManager(InputManager inputManager) { this.inputManager = inputManager; } ///////////////////////// /** * Fire scene change event. * * @param sc the sc */ protected void fireSceneChangeEvent(SceneChangeEvent sc) { for (ISceneChangeListener listener : sceneChangedListeners){ listener.processSceneChangeEvent(sc); } } /** * Adds a scene change listener. * * @param listener the listener */ public synchronized void addSceneChangeListener(ISceneChangeListener listener){ if (!this.sceneChangedListeners.contains(listener)){ sceneChangedListeners.add(listener); } } /** * Removes the scene change listener. * * @param listener the listener */ public synchronized void removeSceneChangeListener(ISceneChangeListener listener){ if (sceneChangedListeners.contains(listener)){ sceneChangedListeners.remove(listener); } } /** * Gets the scene change listeners. * * @return the scene change listeners */ public synchronized ISceneChangeListener[] getSceneChangeListener(){ return (ISceneChangeListener[])sceneChangedListeners.toArray(new ISceneChangeListener[this.sceneChangedListeners.size()]); } ///////////////////////////////// /** * Gets the class name. * * @author C.Ruff */ public static class CurrentClassGetter extends SecurityManager { /** * Gets the class name. * * @return the class name */ public String getClassName() { return getClassContext()[2].getName(); //FIXME is this reliable to always work? } } }