/***********************************************************************
* mt4j Copyright (c) 2008 - 2009 Christopher 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.components.visibleComponents.widgets;
import java.util.HashMap;
import javax.media.opengl.GL;
import org.mt4j.MTApplication;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.input.inputData.AbstractCursorInputEvt;
import org.mt4j.input.inputData.InputCursor;
import org.mt4j.input.inputData.MTInputEvent;
import org.mt4j.input.inputProcessors.globalProcessors.AbstractGlobalInputProcessor;
import org.mt4j.input.inputProcessors.globalProcessors.CursorTracer;
import org.mt4j.sceneManagement.Iscene;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.math.Plane;
import org.mt4j.util.math.Tools3D;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;
import org.mt4j.util.opengl.GLFboStack;
import org.mt4j.util.opengl.GLFBO;
import org.mt4j.util.opengl.GLStencilUtil;
import org.mt4j.util.opengl.GLTexture;
import processing.core.PGraphics;
import processing.opengl.PGraphicsOpenGL;
/**
* The Class MTSceneTexture. This class allows to display a scene from within another scene.
* The scene will be displayed as a textured rectangle. This is only supported using the OpenGL renderer
* and a graphics card supporting the frame buffer object extension.
*
* @author Christopher Ruff
*/
public class MTSceneTexture extends MTRectangle {
private GLFBO fbo;
private Iscene scene;
private MTApplication app;
private Plane p;
private HashMap<InputCursor, InputCursor> oldCursorToNewCursor;
//TODO kind of a hack
private long lastUpdateTime;
private boolean maximized;
private MTSceneMenu sceneMenu;
public MTSceneTexture(MTApplication pa, float x, float y, Iscene theScene){
this(pa, x, y, Math.round(MT4jSettings.getInstance().getWindowWidth() * 0.6f), Math.round(MT4jSettings.getInstance().getWindowHeight() * 0.6f), theScene);
}
public MTSceneTexture(MTApplication pa, float x, float y, int fboWidth, int fboHeight, Iscene theScene){
super(x, y, 0, MT4jSettings.getInstance().getWindowWidth(), MT4jSettings.getInstance().getWindowHeight(), pa);
this.scene = theScene;
this.app = pa;
this.maximized = false;
//Disable the scene's global input processors. We will be redirecting the input
//from the current scene to the window scene
pa.getInputManager().disableGlobalInputProcessors(scene);
//Create FBO
// this.fbo = new GLFBO(pa, pa.width, pa.height);
this.fbo = new GLFBO(pa, fboWidth, fboHeight);
//Attach texture to FBO to draw into
GLTexture tex = fbo.addNewTexture();
//Invert y texture coord (FBO texture is flipped)
Vertex[] v = this.getVerticesLocal();
for (int i = 0; i < v.length; i++) {
Vertex vertex = v[i];
if (vertex.getTexCoordV() == 1.0f){
vertex.setTexCoordV(0.0f);
}else if (vertex.getTexCoordV() == 0.0f){
vertex.setTexCoordV(1.0f);
}
}
this.setVertices(v);
//Apply the texture to this component
this.setTexture(tex);
// //Scale texture coords if using TEXTURE_RECTANGLE_ARB extension
// if (!Tools3D.isPowerOfTwoDimension(tex)){
// Tools3D.scaleTextureCoordsForRectModeFromNormalized(tex, this.getGeometryInfo().getVertices());
// this.setTextureMode(PConstants.IMAGE);
// //Update the texture buffer!
// this.getGeometryInfo().updateTextureBuffer(this.isUseVBOs());
// }
//REMOVE?
this.setUseDirectGL(true);
// this.setBoundsPickingBehaviour(BOUNDS_CHECK_THEN_GEOMETRY_CHECK);
//Plane to check intersections with
this.p = new Plane(new Vector3D(x,y,0) , new Vector3D(0,0,1));
//Mapping of this scenes inputCursors to the newly created cursors of the window scene
this.oldCursorToNewCursor = new HashMap<InputCursor, InputCursor>();
this.lastUpdateTime = 0;
//Draw this component and its children above
//everything previously drawn and avoid z-fighting
// this.setDepthBufferDisabled(true); //FIXME this wont work well when the scene is 3D!
// scene.getCanvas().setDepthBufferDisabled(false);
getFbo().clear(true, 0, 0, 0, 0, true);
pa.invokeLater(new Runnable() {
//Do the next frame because if MTSceneTexture is created in the first scene,
//addScene() will make the scene texture scene the current scene which we dont want!
public void run() {
//Add it to the scene if it isnt already -> we cant destroy the scene later if it isnt in the app
app.addScene(scene);
}
});
}
@Override
public void updateComponent(long timeDelta) {
super.updateComponent(timeDelta);
this.lastUpdateTime = timeDelta;
}
@Override
public void drawComponent(PGraphics g){
PGraphicsOpenGL pgl = (PGraphicsOpenGL)g;
GL gl = pgl.gl;
// boolean b = false;
// if (GLStencilUtil.getInstance().isClipActive()){
// GLStencilUtil.getInstance().endClipping(gl);
// b = true;
// }
fbo.startRenderToTexture();
//Change blending mode to avoid artifacts from alpha blending at antialiasing for example
// gl.glBlendFuncSeparate(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA, GL.GL_ZERO, GL.GL_ONE);
gl.glBlendFuncSeparate(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA, GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA);
// /*
boolean clipping = false;
if (GLStencilUtil.getInstance().isClipActive()){
clipping = true;
gl.glPushAttrib(GL.GL_STENCIL_BUFFER_BIT);
gl.glClearStencil(GLStencilUtil.getInstance().stencilValueStack.peek());
gl.glClear(GL.GL_STENCIL_BUFFER_BIT);
// gl.glDisable(GL.GL_STENCIL_TEST);
}
// */
// gl.glEnable(gl.GL_ALPHA_TEST);
// gl.glAlphaFunc(gl.GL_GREATER, 0.0f);
// gl.glDisable(gl.GL_ALPHA_TEST);
//Draw scene to texture
scene.drawAndUpdate(pgl, this.lastUpdateTime);
// /*
if (clipping){
gl.glPopAttrib();
}
// */
// GLStencilUtil.getInstance().endClipping(gl, this);
fbo.stopRenderToTexture();
if (GLFboStack.getInstance().peekFBO() == 0)
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); //Restore default blend mode //FIXME TEST -> neccessary?
//FIXME NOT NEEDED!? sufficient to call glGenerateMipmapEXT at texture creation!?
//TODO I actually think its necessary to call each time after rendering to the texture! But only for POT dimensions!?
/*
GLTexture tex = (GLTexture) this.getTexture();
gl.glBindTexture(tex.getTextureTarget(), tex.getTextureID());
gl.glGenerateMipmapEXT(tex.getTextureTarget());
gl.glBindTexture(tex.getTextureTarget(), 0);
*/
super.drawComponent(g);
}
/*TODO
* - fehler wenn gedreht x,y, axis und dann scale - wegen shearing?
* - fehler scheint von rotation zu kommen nachdem rotiert x,y,
*
* - progressbar sizes wrong bei 512,512
*
* - FBO: mehrere texture targets wenn supported erm�glichen
*
* - FBO: multisampling fbo optional wenn supported machen siehe glgraphicsoffscreen
*
* - FBO: bei RECTANGLE dimensions keine mipmaps m�glich??
*
* - FBO: hardware fbo mit glCopyTex2D
*
* - nur sceneDrawAndUpdate() wenn sich was ver�ndert hat - sonst kann man einfach alte textur lassen! scene.invalidate()?
*
* (- wenn camera changed richtig picken in scene)
* */
@Override
public boolean processInputEvent(MTInputEvent inEvt) {
//We have to retarget inputevents for this component to the windowed scene
if (inEvt instanceof AbstractCursorInputEvt){
AbstractCursorInputEvt posEvt = (AbstractCursorInputEvt)inEvt;
float x = posEvt.getPosX();
float y = posEvt.getPosY();
float newX = 0;
float newY = 0;
//Check intersection with infinite plane, this rect lies in
Vector3D interSP = p.getIntersectionLocal(this.globalToLocal(Tools3D.getCameraPickRay(app, this, x, y)));
if (interSP != null){
//System.out.println(interSP);
newX = interSP.x;
newY = interSP.y;
}
AbstractCursorInputEvt newEvt = null;
switch (posEvt.getId())
{
case AbstractCursorInputEvt.INPUT_DETECTED:{
InputCursor newCursor = new InputCursor();
try {
newEvt = (AbstractCursorInputEvt) posEvt.clone();
newEvt.setPositionX(newX);
newEvt.setPositionY(newY);
// newCursor.addEvent(newEvt);
newEvt.setCursor(newCursor);
newEvt.preFire();
//Note: We dont set a target for the event! this can be
//handled newly in the wondowed scenes InputRetargeter processor
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
this.oldCursorToNewCursor.put(posEvt.getCursor(), newCursor);
//TODO checken ob cursor bereits events enth�lt - �berhaupt m�glich?..
//ELSE -> CLONE AND ADD ALL OLD EVENTS TO THE NEW CURSOR!
}break;
case AbstractCursorInputEvt.INPUT_UPDATED:{
InputCursor newCursor = this.oldCursorToNewCursor.get(posEvt.getCursor());
if (newCursor != null){
try {
newEvt = (AbstractCursorInputEvt) posEvt.clone();
newEvt.setPositionX(newX);
newEvt.setPositionY(newY);
// newCursor.addEvent(newEvt);
newEvt.setCursor(newCursor);
newEvt.preFire();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}else{
System.err.println("Couldnt find new cursor!");
}
}break;
case AbstractCursorInputEvt.INPUT_ENDED:{
InputCursor newCursor = this.oldCursorToNewCursor.remove(posEvt.getCursor());
if (newCursor != null){
try {
newEvt = (AbstractCursorInputEvt) posEvt.clone();
newEvt.setPositionX(newX);
newEvt.setPositionY(newY);
// newCursor.addEvent(newEvt);
newEvt.setCursor(newCursor);
newEvt.preFire();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}else{
System.err.println("Couldnt find new cursor!");
}
}break;
default:
break;
}
AbstractCursorInputEvt evtToFire = (newEvt != null) ?
newEvt :
posEvt;
//Send similar event to the windowed scenes global input processors
AbstractGlobalInputProcessor[] globalAnalyzer = app.getInputManager().getGlobalInputProcessors(scene);
for (int i = 0; i < globalAnalyzer.length; i++) {
AbstractGlobalInputProcessor a = globalAnalyzer[i];
if (!(a instanceof CursorTracer)){
//Hack because actually processors are disabled so they dont recieve
//input directly, so we dont call processInputEvt()!
a.processInputEvtImpl(evtToFire);
}
}
return false;
}else{
return super.processInputEvent(inEvt);
}
// return super.processInputEvent(inEvt);
// scene.getCanvas().processInputEvent(inEvt);
// return true;
}
// @Override
// public boolean processGestureEvent(MTGestureEvent gestureEvent) {
// return super.processGestureEvent(gestureEvent);
// }
public Iscene getScene(){
return this.scene;
}
public GLFBO getFbo() {
return fbo;
}
public void addSceneMenu(){
if (sceneMenu == null){
float menuWidth = 64;
float menuHeight = 64;
// this.sceneMenu = new MTSceneMenu(this, app.width-menuWidth/2f, 0-menuHeight/2f, menuWidth, menuHeight, app);
// this.sceneMenu = new MTSceneMenu(this, app.width-menuWidth, 0, menuWidth, menuHeight, app);
this.sceneMenu = new MTSceneMenu(this, app.width-menuWidth, app.height-menuHeight, menuWidth, menuHeight, app);
this.sceneMenu.setVisible(false);
}
this.sceneMenu.addToScene();
if (maximized){
this.sceneMenu.setVisible(true);
}
}
public void destroySceneMenu(){
this.sceneMenu.removeFromScene();
this.sceneMenu.destroy();
this.sceneMenu = null;
}
/**
* Maximize.
*/
public void maximize() {
app.pushScene();
app.addScene(scene);
if (app.changeScene(scene)){
maximized = true;
this.addSceneMenu();
}
}
/**
* Restore.
*
* @return true, if successful
*/
public boolean restore(){
if(app.popScene() /*&& app.removeScene(scene)*/){
maximized = false;
//FIXME TEST remove the in-scene window menu
if (sceneMenu != null){
this.sceneMenu.setVisible(false);
this.sceneMenu.removeFromScene();
}
return true;
}else{
return false;
}
}
@Override
protected void destroyComponent() {
super.destroyComponent();
this.getFbo().destroy();
//Restore to pop to another scene before destroying this
if (maximized){
restore();
}
// //FIXME Destroy and remove scene -> can we safely do this? if the scene is used elsewhere -> problem
// //Problem if sceneTexture is used in transitions and destroyed after -> scene is destroyed!!
//But if we dont destroy it the scene could linger around forever if used elsewhere!
// scene.destroy();
//Destroy scene Menu
if (sceneMenu != null){
this.sceneMenu.removeFromScene();
sceneMenu.destroy();
}
}
//TODO if windowed
//- maximize
//- close
//TODO
//IF CLOSE:
//popScene()
//destroy() scene window
//remove scene from MTApp
//scene.destroy()
//IF RESTORE:
//popScene();
//remove window menu from scene's canvas
}