/***********************************************************************
* 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.util.opengl;
import java.util.Stack;
import javax.media.opengl.GL;
import org.mt4j.components.visibleComponents.AbstractVisibleComponent;
import processing.opengl.PGraphicsOpenGL;
/**
* Abstracts the OpenGL stencil buffer for
* clipping use.
*
* @author Christopher Ruff
*/
public class GLStencilUtil {
/** The instance. */
public static GLStencilUtil instance;
/** The initialized. */
boolean initialized = false;
/** The stencil value stack. */
public static Stack<Integer> stencilValueStack = new Stack<Integer>();
private static final int initialStencilValue = 6; //arbitrary value to save 1-5 for other operations..
/** The current stencil value. */
public static int currentStencilValue = initialStencilValue;
static{
stencilValueStack.push(currentStencilValue);
}
//TODO record some methods/commands in displayLists?
/**
* Gets the single instance of StencilStack.
*
* @return single instance of StencilStack
*/
public static GLStencilUtil getInstance(){
if (instance != null){
return instance;
}else{
instance = new GLStencilUtil();
return instance;
}
}
/*
* Stencil comparison in stencil func explained:
* gl.glStencilFunc( <func comparison>, <referenceValue>, <mask>);
* boolean result = (referenceValue & mask) <func comparison> (stencilValue & mask)
*/
/**
* Sets up the stencil buffer.
* After calling this method, every draw command will be written into
* the stencil buffer only, marking the clipping area for later.
*
* @param gl the gl
*/
public void beginDrawClipShape(GL gl){ //begin draw clip shape
// gl.glPushAttrib(GL.GL_STENCIL_BUFFER_BIT | GL.GL_STENCIL_TEST); //FIXME do only at initialization??
gl.glPushAttrib(GL.GL_STENCIL_BUFFER_BIT); //FIXME do only at initialization??
if (!initialized){
// gl.glPushAttrib(GL.GL_STENCIL_BUFFER_BIT | GL.GL_STENCIL_TEST);
//Enable stencilbuffer
gl.glClearStencil(stencilValueStack.peek());
gl.glClear(GL.GL_STENCIL_BUFFER_BIT);
gl.glEnable(GL.GL_STENCIL_TEST);
// gl.glStencilMask (0x0000000D);
}
int currentStencilValue = stencilValueStack.peek();
//Dont draw into the color or depth buffer while drawing the clip shape
gl.glColorMask(false,false,false,false);
gl.glDisable(GL.GL_BLEND);
gl.glDepthMask(false);//remove..?
if (!initialized){
initialized = true;
//If were at the top level = nothing written into stencil buffer yet
//draw mask value into buffer regardless if stencilfunc suceeds
gl.glStencilFunc (GL.GL_ALWAYS, currentStencilValue, currentStencilValue);
}else{
//= draw mask value into stencil only where the current/last stencil value is equal to the current stencil value?
//we may not write into the stencil somehwhere else -> the parent may have also clipped something
//-> write only where the parent clip wrote its stencil clip mask and dont go beyond that
gl.glStencilFunc (GL.GL_EQUAL, currentStencilValue, currentStencilValue);
}
//We write the current stencil value +1 ! into the stencil buffer where the
//stencil func succeeds
//This marks the area where we are allowed to draw the clipped shape later
gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_INCR); //FIXME also write stencil value if depth test fails?
//- we increment the value on the stencil stack
//so it correlates with the incremented value in the stencil buffer
stencilValueStack.push(++currentStencilValue);
}
/**
* This method should be called after drawing the clipping shape into the stencil buffer
* using <code>beginDrawClipShape</code>.
* After invoking <code>beginDrawClipped</code>, we can now draw only where we drew the clipping shape.
*
* @param gl the gl
*/
public void beginDrawClipped(GL gl){ //draw clipped
int incrementedStencilValue = stencilValueStack.peek();
//TODO instead of setting depth, blend etc, use glPush/Popattrib !?
//Prepare draw where the value of the stencil buffer is
//the same as the current stencil stack value
//(the value which was written into the buffer at "beginDrawClippingShape()"
gl.glDepthMask(true);
gl.glEnable (GL.GL_BLEND);
// gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); //FIXME NEEDED?
gl.glColorMask(true, true, true, true);
gl.glStencilFunc(GL.GL_EQUAL, incrementedStencilValue, incrementedStencilValue);
gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_KEEP);
}
/**
* Ends the drawing to the clipped area only.
*
* @param gl the gl
*/
public void endClipping(GL gl){
this.endClipping(gl, null);
}
public boolean isClipActive(){
return stencilValueStack.size() > 1;
}
/**
* Ends the drawing to the clipped area and restores the previously used
* clip area if there is one.
*
* @param gl the gl
* @param clipShape the clip shape
*/
public void endClipping(GL gl, AbstractVisibleComponent clipShape){ //stop clipping
//Remove the top/last used stencil mask value from the stack
int currentStencilValue = stencilValueStack.pop();
//At the end of every stencil clip hierarchy recursion
//set this so that at the next hierarchy,
//the stencil is completely cleared again
if (stencilValueStack.size() == 1){
initialized = false;
// //Restore stencil attributes, disables stencil test and restores stencil buffer bit
// gl.glPopAttrib();
}else if (stencilValueStack.isEmpty()){
System.err.println("Too many calls to " + this.getClass().getName() + ".endClipping() !");
stencilValueStack.push(initialStencilValue);
}else{
if (clipShape != null){
gl.glColorMask(false,false,false,false);
gl.glDepthMask(false);//remove..?
// /*
//ORIGINAL
//Decrease stencil value again where we increased it at drawing the clipping shape
//(so the stencil values are same as before drawing the clip shape)
gl.glStencilFunc (GL.GL_EQUAL, currentStencilValue, currentStencilValue);
gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_DECR);
//FIXME this can be bad for performance if the clipshape is complex
clipShape.drawComponent(clipShape.getRenderer().g);
// */
gl.glDepthMask(true);
gl.glColorMask(true, true, true, true);
}else{
//TODO use full screen quad technique if clipshape vertices > 10 ?
// /*
//Draw fullscreen quad to restore stencil
//->peek last value and draw fullscreen quad writing last peek value everywhere
//where lastPeekValue lesser to stencil value?
//Option 1, replace stencil value with previous value if stencil is higher than previous value
int last = stencilValueStack.peek();
gl.glStencilFunc (GL.GL_LESS, last, 0xFF);
gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_REPLACE);
//Option 2, decrement at the last pushed value
// gl.glStencilFunc (GL.GL_EQUAL, currentStencilValue, currentStencilValue);
// gl.glStencilOp(GL.GL_KEEP, GL.GL_KEEP, GL.GL_DECR);
((PGraphicsOpenGL)clipShape.getRenderer().g).beginGL();
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glPushMatrix();
gl.glLoadIdentity();
gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glPushMatrix();
gl.glLoadIdentity();
gl.glBegin(GL.GL_QUADS);
gl.glVertex2f(-1, -1);
gl.glVertex2f(1, -1);
gl.glVertex2f(1, 1);
gl.glVertex2f(-1, 1);
gl.glEnd();
gl.glPopMatrix();
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glPopMatrix();
gl.glMatrixMode(GL.GL_MODELVIEW);
((PGraphicsOpenGL)clipShape.getRenderer().g).endGL();
// */
}
}
//Restore stencil attributes, disables stencil test and restores stencil buffer bit
gl.glPopAttrib(); //FIXME do this only when stack is emtpied?
}
}