/*
* Copyright (c) 2005 Sun Microsystems, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
* ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
* DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use
* in the design, construction, operation or maintenance of any nuclear
* facility.
*
* Sun gratefully acknowledges that this software was originally authored
* and developed by Kenneth Bradley Russell and Christopher John Kline.
*/
package demos.xtrans;
import jogamp.opengl.awt.Java2D;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import com.jogamp.opengl.DebugGL2;
import com.jogamp.opengl.GL;
import com.jogamp.opengl.GLProfile;
import com.jogamp.opengl.GL2ES1;
import com.jogamp.opengl.GL2;
import com.jogamp.opengl.GLContext;
import com.jogamp.opengl.GLDrawableFactory;
import com.jogamp.opengl.glu.GLU;
/** A JDesktopPane subclass supporting Accelerated Transitions (XT) of
* the components contained within.
*
* @author Kenneth Russell
*/
public class XTDesktopPane extends OffscreenDesktopPane {
private GLContext j2dContext;
private Object j2dContextSurfaceIdentifier;
private Rectangle oglViewport;
private XTTransitionManager transitionManager = new XTBasicTransitionManager();
private boolean reallyRemove;
private boolean alwaysRedraw;
static class TransitionInfo {
boolean isIn;
Component target;
long startTime;
XTTransition trans;
TransitionInfo(boolean isIn,
Component target,
long startTime,
XTTransition trans) {
this.isIn = isIn;
this.target = target;
this.startTime = startTime;
this.trans = trans;
}
}
private java.util.List/*<TransitionInfo>*/ transitions = new ArrayList();
private float TRANSITION_DURATION = 300.0f;
private int textureTarget = GL.GL_TEXTURE_2D;
private GLU glu = new GLU();
private GLProfile glProfile = GLProfile.getDefault();
/** Creates a new accelerated transition desktop pane. */
public XTDesktopPane() {
super();
if (!Java2D.isOGLPipelineActive()) {
throw new RuntimeException("XTDesktopPane requires new Java2D/JOGL support in Java SE 6 and -Dsun.java2d.opengl=true");
}
setDesktopManager(new XTDesktopManager());
}
/** Overridden to use a transition to display the given
component. */
protected void addImpl(Component c, Object constraints, int index) {
super.addImpl(c, constraints, index);
getOffscreenDesktopManager().layoutOffscreenBuffer(this);
// When animating the component's transition, center the
// perspective projection around the center of the newly-added
// component so that the perspective effects appear symmetric.
// This amounts to moving the viewport so the component is in the
// center.
addTransition(true, c,
transitionManager.createTransitionForComponent(c,
true,
getOGLViewport(),
computeViewportOffsetToCenterComponent(c, getOGLViewport()),
getXTDesktopManager().getOpenGLTextureCoords(c)));
}
/** Overridden to use an animated transition to remove the passed
component. */
public void remove(int index) {
if (reallyRemove) {
super.remove(index);
} else {
addRemoveTransition(getRealComponent(getComponent(index)));
}
}
/** Overridden to use an animated transition to remove the passed
component. */
public void remove(Component c) {
if (reallyRemove) {
super.remove(c);
} else {
addRemoveTransition(getRealComponent(c));
}
}
/** Causes the given component to really be removed from this
desktop pane. Called when the removal transition is complete. */
protected void removeImpl(Component c) {
reallyRemove = true;
try {
remove(c);
} finally {
reallyRemove = false;
}
}
/** Overridden to draw the child components, including any animated
transitions, using OpenGL. */
protected void paintChildren(final Graphics g) {
// FIXME: this is a hack to get repainting behavior to work
// properly when we specify that optimized drawing is disabled (so
// that childrens' repaint requests will trickle up to us via the
// Animator) but need to descend to repaint our children --
// currently don't know how to distinguish between repaint events
// propagated up to us and those initiated by the children (which
// typically go through the OffscreenComponentWrapper's
// getGraphics() method and implicitly cause a redraw of all child
// components as well as the desktop)
if (alwaysRedraw) {
getOffscreenDesktopManager().setNeedsRedraw();
}
// Update desktop manager's offscreen buffer if necessary
getOffscreenDesktopManager().updateOffscreenBuffer(this);
// Draw textured quads using JOGL over current contents of back
// buffer
final Component[] components = getRealChildComponents();
final ArrayList expiredTransitions = new ArrayList();
Java2D.invokeWithOGLContextCurrent(g, new Runnable() {
public void run() {
// Get valid Java2D context
if (j2dContext == null ||
j2dContextSurfaceIdentifier != Java2D.getOGLSurfaceIdentifier(g)) {
j2dContext = GLDrawableFactory.getFactory(glProfile).createExternalGLContext();
j2dContext.setGL(new DebugGL2(j2dContext.getGL().getGL2()));
j2dContextSurfaceIdentifier = Java2D.getOGLSurfaceIdentifier(g);
}
j2dContext.makeCurrent(); // No-op
try {
GL2 gl = j2dContext.getGL().getGL2();
// Figure out where JDesktopPane is on the Swing back buffer
Rectangle oglRect = Java2D.getOGLViewport(g, getWidth(), getHeight());
// Cache this value for adding transitions later
oglViewport = new Rectangle(oglRect);
// Set up perspective projection so we can do some subtle
// 3D effects. We set up the view volume so that at z=0
// the lower-left coordinates of the desktop are (0, 0)
// and the upper right coordinates are
// (oglRect.getWidth(), oglRect.getHeight()). The key here
// is to decide on the field of view and then figure out
// how far back we have to put the eye point in order for
// this to occur.
double fovy = 30.0; // degrees
double w = oglRect.getWidth();
double h = oglRect.getHeight();
// d is the distance from the eye point to the image plane
// (z=0)
double d = (h / 2) / Math.tan(Math.toRadians(fovy) / 2);
double near = d - (h / 2);
double far = d + (h / 2);
gl.glViewport(oglRect.x, oglRect.y, oglRect.width, oglRect.height);
gl.glMatrixMode(GL2ES1.GL_PROJECTION);
gl.glPushMatrix();
gl.glLoadIdentity();
glu.gluPerspective(fovy, (w / h), near, far);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPushMatrix();
gl.glLoadIdentity();
gl.glMatrixMode(GL2ES1.GL_MODELVIEW);
gl.glPushMatrix();
gl.glLoadIdentity();
double eyeX = w / 2;
double eyeY = h / 2;
// Object x and y are the same as eye x and y since we're
// looking in the -z direction
glu.gluLookAt(eyeX, eyeY, d,
eyeX, eyeY, 0,
0, 1, 0);
// Set up a scissor box so we don't blow away other
// components if we shift around the viewport to get the
// animated transitions' perspective effects to be
// centered
gl.glEnable(GL.GL_SCISSOR_TEST);
Rectangle r = Java2D.getOGLScissorBox(g);
if (r != null) {
gl.glScissor(r.x, r.y, r.width, r.height);
}
/*
// Orthographic projection for debugging
gl.glViewport(oglRect.x, oglRect.y, oglRect.width, oglRect.height);
// Set up coordinate system for easy access
gl.glMatrixMode(GL2ES1.GL_PROJECTION);
// System.err.println("oglRect x = " + oglRect.getX());
// System.err.println("oglRect y = " + oglRect.getY());
// System.err.println("oglRect w = " + oglRect.getWidth());
// System.err.println("oglRect h = " + oglRect.getHeight());
gl.glPushMatrix();
gl.glLoadIdentity();
gl.glOrtho(oglRect.getX(), oglRect.getX() + oglRect.getWidth(),
oglRect.getY(), oglRect.getY() + oglRect.getHeight(),
-1,
1);
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPushMatrix();
gl.glLoadIdentity();
gl.glMatrixMode(GL2ES1.GL_MODELVIEW);
gl.glPushMatrix();
gl.glLoadIdentity();
*/
// Enable and bind texture corresponding to internal frames' back buffer
gl.glBindTexture(textureTarget, getXTDesktopManager().getOpenGLTextureObject());
gl.glEnable(textureTarget);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_S, GL2.GL_CLAMP);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_WRAP_T, GL2.GL_CLAMP);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
gl.glTexParameteri(textureTarget, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
gl.glEnable(GL.GL_BLEND);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);
gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
// Iterate down children in z order bottom-to-top
int compCount = components.length;
long curTime = currentTimeMillis();
for (int i = compCount - 1; i >= 0; i--) {
Component c = components[i];
// Find transition for this component
TransitionInfo info = transitionForComponent(c);
if (info != null) {
gl.glPushMatrix();
// When animating the component's transition, center the
// perspective projection around the center of the newly-added
// component so that the perspective effects appear symmetric.
// This amounts to moving the viewport so the component is in the
// center.
Point viewportOffset = computeViewportOffsetToCenterComponent(c, getOGLViewport());
gl.glViewport(oglRect.x + viewportOffset.x,
oglRect.y + viewportOffset.y,
oglRect.width,
oglRect.height);
// Update it
float percent = clamp((curTime - info.startTime) / TRANSITION_DURATION, 0.0f, 1.0f);
XTTransition trans = info.trans;
trans.update(percent);
trans.draw(gl);
// See whether the transition has expired
if (percent == 1.0f) {
transitions.remove(info);
expiredTransitions.add(info);
}
gl.glPopMatrix();
// Put the viewport back where it was
gl.glViewport(oglRect.x, oglRect.y, oglRect.width, oglRect.height);
} else {
// For each one, get the OpenGL texture coordinates on the offscreen OpenGL texture
Rectangle2D oglTexCoords = getXTDesktopManager().getOpenGLTextureCoords(c);
Rectangle bounds = c.getBounds();
int cx = bounds.x;
int cy = bounds.y;
int cw = bounds.width;
int ch = bounds.height;
float tx = (float) oglTexCoords.getX();
float ty = (float) oglTexCoords.getY();
float tw = (float) oglTexCoords.getWidth();
float th = (float) oglTexCoords.getHeight();
float vx = oglRect.x;
float vy = oglRect.y;
float vw = oglRect.width;
float vh = oglRect.height;
// Draw a quad per component
gl.glBegin(GL.GL_TRIANGLES);
gl.glColor4f(1, 1, 1, 1);
// Triangle 1
gl.glTexCoord2f(tx, ty + th);
gl.glVertex3f (cx, vh - cy, 0);
gl.glTexCoord2f(tx, ty);
gl.glVertex3f (cx, vh - cy - ch, 0);
gl.glTexCoord2f(tx + tw, ty + th);
gl.glVertex3f (cx + cw, vh - cy, 0);
// Triangle 2
gl.glTexCoord2f(tx + tw, ty + th);
gl.glVertex3f (cx + cw, vh - cy, 0);
gl.glTexCoord2f(tx, ty);
gl.glVertex3f (cx, vh - cy - ch, 0);
gl.glTexCoord2f(tx + tw, ty);
gl.glVertex3f (cx + cw, vh - cy - ch, 0);
gl.glEnd();
}
}
gl.glFlush();
gl.glDisable(textureTarget);
gl.glDisable(GL.GL_BLEND);
gl.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);
gl.glMatrixMode(GL2ES1.GL_PROJECTION);
gl.glPopMatrix();
gl.glMatrixMode(GL.GL_TEXTURE);
gl.glPopMatrix();
gl.glMatrixMode(GL2ES1.GL_MODELVIEW);
gl.glPopMatrix();
gl.glFinish();
} finally {
j2dContext.release();
}
}
});
for (Iterator iter = expiredTransitions.iterator(); iter.hasNext(); ) {
TransitionInfo info = (TransitionInfo) iter.next();
if (!info.isIn) {
removeImpl(info.target);
repaint();
}
}
if (!transitions.isEmpty()) {
repaint();
}
}
/** Overridden from parent to disable optimized drawing so that we
get correct rendering results with embedded GLJPanels */
public boolean isOptimizedDrawingEnabled() {
return false;
}
/** Returns the XTDesktopManager for this desktop pane. */
public XTDesktopManager getXTDesktopManager() {
return (XTDesktopManager) getDesktopManager();
}
/** Returns the transition manager for this desktop pane. By default
this is an XTBasicTransitionManager. */
public XTTransitionManager getTransitionManager() {
return transitionManager;
}
/** Sets the transition manager for this desktop pane. By default
this is an XTBasicTransitionManager. */
public void setTransitionManager(XTTransitionManager manager) {
transitionManager = manager;
}
/** Workaround to get painting behavior to work properly in some
situations. */
public void setAlwaysRedraw(boolean onOrOff) {
alwaysRedraw = onOrOff;
}
/** Workaround to get painting behavior to work properly in some
situations. */
public boolean getAlwaysRedraw() {
return alwaysRedraw;
}
/** Returns the transition corresponding to the passed Component, or
null if no transition is currently active for this component. */
private TransitionInfo transitionForComponent(Component c) {
for (Iterator iter = transitions.iterator(); iter.hasNext(); ) {
TransitionInfo info = (TransitionInfo) iter.next();
if (info.target == c) {
return info;
}
}
return null;
}
/** Adds a transition for the specified component. An "out"
transition will automatically cause the component to be removed
after it has completed running. */
protected void addTransition(boolean isIn,
Component target,
XTTransition trans) {
TransitionInfo info = new TransitionInfo(isIn,
target,
currentTimeMillis(),
trans);
transitions.add(info);
}
/** Adds a removal transition for the given component. */
protected void addRemoveTransition(Component target) {
addTransition(false,
target,
transitionManager.createTransitionForComponent(target,
false,
getOGLViewport(),
computeViewportOffsetToCenterComponent(target, getOGLViewport()),
getXTDesktopManager().getOpenGLTextureCoords(target)));
}
/** Computes the offset applied to the OpenGL viewport to center the
given component in the viewport. This is used to make the
perspective effects appear symmetric about the component. */
protected Point computeViewportOffsetToCenterComponent(Component c,
Rectangle oglViewport) {
Rectangle bounds = c.getBounds();
return new Point(bounds.x + ((bounds.width - oglViewport.width) / 2),
-bounds.y + ((oglViewport.height - bounds.height) / 2));
}
/** Clamps the given value between the specified minimum and
maximum. */
protected static float clamp(float val, float min, float max) {
return Math.min(max, Math.max(min, val));
}
/** Returns the current time in milliseconds. */
protected static long currentTimeMillis() {
// Avoid 1.5 compilation dependencies since no perceived
// improvement by changing this
// return System.nanoTime() / 1000000;
return System.currentTimeMillis();
}
/** Returns the OpenGL viewport corresponding to this desktop pane. */
protected Rectangle getOGLViewport() {
if (oglViewport != null) {
return oglViewport;
}
Rectangle b = getBounds();
return new Rectangle(0, 0, b.width, b.height);
}
}