/***********************************************************************
* 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?
}
}
}