/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions 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 the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.ui.swingui; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List; import org.jagatoo.image.DirectBufferedImage; import org.jagatoo.opengl.enums.DrawMode; import org.jagatoo.opengl.enums.FaceCullMode; import org.openmali.vecmath2.Colorf; import org.xith3d.scenegraph.Group; import org.xith3d.scenegraph.Node; import org.xith3d.scenegraph.PolygonAttributes; import org.xith3d.scenegraph.RenderingAttributes; import org.xith3d.scenegraph.TextureAttributes; import org.xith3d.scenegraph.TransparencyAttributes; import org.xith3d.scenegraph.View; import org.xith3d.utility.logging.X3DLog; /** * An overlay is 3d geometry which is aligned with the image plate. This is * designed to be as simple as possible for the developer to use. All that is * needed is to extend this class and override the paint function to update the * image which will be written displayed on the screen. * * As a note: Displaying large overlays can use a *huge* amount of texture memory. * If you created a pane which was 1024x768 you would consume over 3 mb of texture memory. * * Another thing to realize is that textures have to be a power of two. Because of this * an overlay which was 513 x 257 would normally have to be generated using 1024 x 512. Unlike * most textures, overlays cannot tolerate stretching and interpolation because of the fuzzyness * which would result. So what the Xith overlay system attempts to do is break the overlay up * into small pieces so that extra texture memory is not wasted. You are given a canvas which * represents the entire overlay. After you finish updating it, the image is ripped apart and * written to the underlying textures. Each of these underlying textures are double-buffered, * so that switching them to the Xith3d engine is quick and does not involve having to continuously * allocate and free large BufferedImages. This means, of course, that for every overlay you * use 3 times (width * height * 4bytes) of system memory. So a chatbox which was 512 x 200 * would use 3 * ( 512 * 200 *4) bytes or 1.2 MB of system memory. * * Updates to the actual textures are done within a behavior within a frame. This is very * fast because all that is happening is that the buffers are getting swapped. * We have to be certain that we are not in the process of copying the big buffer into the back * buffer at the same time the behavior attempts to do a buffer swap. This is handled by the * overlay by not updating texture if we are in the middle of drawing a new one. * The drawback to this is that numerous updates per second to the overlay could result in * several updates not get immediately reflected. But since the area is always completely * redrawn this should not prove to be an issue. Soon as we hit a frame where we are not updating * the buffer then it will be swapped. * * Remember, all you have to do to make translucent or non-square overlays is * to use the alpha channel. * <p> * Originally Coded by David Yazel on Oct 4, 2003 at 9:59:54 PM. * * @author David Yazel */ public class UIOverlay { protected static final Boolean DEBUG = null; public static final int BACKGROUND_NONE = 0; public static final int BACKGROUND_COPY = 1; //private static double consoleZ = 5f; // private static double consoleZ = 0.55f; private BufferedImage canvas; // the large drawing space private boolean clipAlpha; // are we clipping alpha == 0 private boolean blendAlpha; // are we blending alpha < 1 private int imageType; // image types for buffers private List< UISubOverlayBase > subOverlays; // list of SubOverlay nodes protected int width; protected int height; //private double consoleWidth; // width of console //private double consoleHeight; // height of console //private double scale; // scale into world coordinates //private Dimension canvasDim; // the dimensions of the Canvas3d //private Dimension checkDim; // used to check for dimension changes protected Group consoleBG; // branch group for overlay private RenderingAttributes ra = null; private PolygonAttributes pa = null; private TextureAttributes ta = null; private TransparencyAttributes tra = null; // the following are used to synchronize the updates to the backbuffer // The behavior will do a buffer swap if dirty and not painting. private boolean dirty = false; private boolean painting = false; private boolean owned = false; // used for supporting a pre-paint background draw int backgroundMode = BACKGROUND_NONE; private BufferedImage backgroundImage = null; // used for locking access to the painting. private Object mutex; // the location of the overlay in screen coordinates //private int posX; //private int posY; public boolean isOwned() { return ( owned ); } public boolean isOpaque() { return ( !blendAlpha ); } /** * This should be overwritten if you want to add any additional objects or * behaviors or initialization before the overlay goes live */ protected void initialize() { } /** * This sets the position of the overlay in screen coordinates. 0,0 is the * upper left of the screen * * @param x * @param y */ public void setPosition( int x, int y ) { } /** * Return the root of the overlay and its sub-overlays so it can be * added to the scenegraph */ public Node getRoot() { return ( consoleBG ); } /** * This internal method creates the sub-overlays for the overlay. It will * attempt to create an optimal arrangement of textures. */ private void createSubOverlays() { subOverlays = new ArrayList< UISubOverlayBase >(); // calculate the number of columns and their sizes int numCols = 0; int[] cols = new int[ 20 ]; int w = width; while ( w > 0 ) { int p = optimalPower( w, 32 ) - 1; if ( p > w ) { p = w; } cols[ numCols ] = p; w -= cols[ numCols++ ]; } // now calculate the number of rows and their sizes int numRows = 0; int[] rows = new int[ 20 ]; int h = height; while ( h > 0 ) { int p = optimalPower( h, 32 ); if ( p > h ) { p = h; } rows[ numRows ] = p; h -= rows[ numRows++ ]; } // now we have the optimal grid we need to create the sub-overlays. // The bounds passed to the sub-overlay are 0,0 based since they represent // pieces of the big canvas int yStart = 0; int yStop = rows[ 0 ] - 1; //int ly = 0; for ( int row = 0; row < numRows; row++ ) { int xStart = 0; int xStop = cols[ 0 ] - 1; for ( int col = 0; col < numCols; col++ ) { UISubOverlayBase s = new UISubOverlayOptimized( this, xStart, yStart, xStop, yStop ); subOverlays.add( s ); consoleBG.addChild( s.getShape() ); if ( col < ( numCols - 1 ) ) { xStart = xStop + 1; xStop = xStop + cols[ col + 1 ]; } } if ( row < ( numRows - 1 ) ) { yStart = yStop + 1; yStop = yStop + rows[ row + 1 ]; } } } /** * @return an optimal power of two for the value given. It will either * return the largest power of 2 which is less than or equal to the value, OR * it will return a larger power of two as long as the difference between * that and the value is not greater than the threshhold. */ private int optimalPower( int value, int threshhold ) { int n = 2; while ( n < value ) { if ( ( n * 2 ) > 512 ) { return ( 512 ); } if ( ( n * 2 ) > value ) { if ( ( ( n * 2 ) - value ) < threshhold ) { return ( n * 2 ); } return ( n ); } n *= 2; } return ( n ); } /** * @return true if the overlay is clipping when alpha is zero */ public boolean getClipAlpha() { return ( clipAlpha ); } /** * @return true if the overlay is blending alpha. */ public boolean getBlendAlpha() { return ( blendAlpha ); } /** * @return the image type used for the buffered image */ public int getImageType() { return ( imageType ); } /** * Return the rendering attributes shared by all sub-overlays */ public RenderingAttributes getRenderingAttributes() { return ( ra ); } /** * Return the polygon attributes shared by all the sub-overlays */ public PolygonAttributes getPolygonAttributes() { return ( pa ); } /** * Return the texture attributes shared by all the sub-overlays */ public TextureAttributes getTextureAttributes() { return ( ta ); } /** * Return the transparency attributes */ public TransparencyAttributes getTransparencyAttributes() { return ( tra ); } /** * Move the contents of the drawing canvas into the various * backbuffers. */ protected void moveToBackbuffer() { synchronized ( mutex ) { // copy all the buffers to the back buffer painting = true; int n = subOverlays.size(); for ( int i = 0; i < n; i++ ) { UISubOverlayOptimized s = (UISubOverlayOptimized)subOverlays.get( i ); //Rectangle r = new Rectangle( s.lx, s.ly, s.width, s.height ); //if (r.intersects( 0, 0, 50, 50 )) s.update( canvas, s.getBounds() ); } // flag the frame as dirty dirty = true; painting = false; } } /** * Prepares the canvas to be painted. This should only be called internally * or from an owner like the ScrollingOverlay class */ protected Graphics2D getPreppedCanvas() { if ( backgroundMode == BACKGROUND_COPY ) { if ( backgroundImage != null ) { canvas.setData( backgroundImage.getRaster() ); } } Graphics2D g = (Graphics2D)canvas.getGraphics(); return ( g ); } static int testNum = 0; /** * Moves the contents of the buffered image to the canvas and then updates the * back buffers that have changed. * * @param image * @param dirtyAreas */ public void repaintChanged( BufferedImage image, ArrayList< ? > dirtyAreas ) { // move the image onto the canvas int n = subOverlays.size(); // debugging step to highlight changed areas /* { int nn = dirtyAreas.size(); for (int j = 0; j < nn; j++) { Rectangle r = ((UIDirtyRegion)dirtyAreas.get( j )).getAbsDirty(); g.setColor( Color.yellow ); g.drawRect( r.x, r.y, r.width - 1, r.height - 1 ); } } */ // update the changed sub overlays for ( int i = 0; i < n; i++ ) { UISubOverlayOptimized s = (UISubOverlayOptimized)subOverlays.get( i ); Rectangle sbounds = s.getBounds(); int nn = dirtyAreas.size(); //boolean dirtied = false; for ( int j = 0; j < nn; j++ ) { Rectangle r = ( (UIDirtyRegion)dirtyAreas.get( j ) ).getAbsDirty(); if ( r.intersects( sbounds ) ) { //g.setColor( Color.red ); //g.drawRect( sbounds.x, sbounds.y, sbounds.width - 1, sbounds.height - 1 ); s.update( image, r ); //dirtied = true; //System.out.println( "updating sub overlay " + sbounds ); } } //if (!dirtied) // s.updateBackBufferByDraw( canvas ); } //g.dispose(); dirty = true; } public void repaint( BufferedImage image ) { // move the image onto the canvas int n = subOverlays.size(); // update the changed sub overlays for ( int i = 0; i < n; i++ ) { UISubOverlayOptimized s = (UISubOverlayOptimized)subOverlays.get( i ); Rectangle sbounds = s.getBounds(); s.update( image, sbounds ); } dirty = true; } /** * This is called to trigger a repaint of the overlay. This will return once * the back buffer has been built, but before the swap. */ public void repaint() { Graphics2D g = getPreppedCanvas(); paint( g ); g.dispose(); moveToBackbuffer(); } public void getSize( Dimension dim ) { dim.width = width; dim.height = height; } /** * This method is ONLY called from the behavior. This checks to see if it needs * to swap the buffers. If so, it locks the mutex and swaps all the sub-overlay * buffers at once. If this isn't called from a behavior then the sub-buffer swaps * might happen in seperate frames. */ public void update() { if ( dirty && !painting ) { synchronized ( mutex ) { // swap all the sub-buffers int n = subOverlays.size(); for ( int i = 0; i < n; i++ ) { UISubOverlayBase s = subOverlays.get( i ); if ( s.dirty ) { //System.out.println( "Swapping " + s.getBounds() ); s.swap(); } /* else System.out.print( "+" ); */ } dirty = false; //Log.log.println( LogType.EXHAUSTIVE, "Just swapped " + n + " suboverlays" ); } } } /** * Changes the visibility of the overlay. * * @param yes */ public void setVisible( boolean yes ) { //ra.setVisible( yes ); throw new Error( "Implementation removed. Please talk to the author!" ); } /** * This is where the actualy drawing of the window takes place. Override * this to alter the contents of what is show in the window. */ public void paint( Graphics2D g ) { // set up a test pattern for checking for distortions g.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); g.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); /* g.setColor( Color.black ); g.fillRect( 0, 0, width,height ); */ g.setColor( Color.red ); g.fillOval( 1, 1, width - 2, height - 2 ); g.setColor( Color.yellow ); g.drawLine( 0, height - 1, width - 1, height - 1 ); g.setColor( Color.cyan ); g.drawLine( 0, 0, width - 1, 0 ); g.setColor( Color.green ); g.drawLine( 0, 0, 0, height - 1 ); g.setColor( Color.magenta ); g.drawLine( width - 1, 0, width - 1, height - 1 ); } /** * Protected method to get the view which this overlay is matching */ protected View getView() { return ( null ); //return ( c3d.getView() ); } /** * Allows an derived class to add a behavior or object to the same transform * group that the sub-overlays use */ protected void attachNode( Node node ) { consoleBG.addChild( node ); } /** * Internal routine to return an appropriate buffered image for the overlay * size and format. */ private DirectBufferedImage getAppropriateImage() { if ( clipAlpha || blendAlpha ) { return ( DirectBufferedImage.makeDirectImageRGBA( width, height ) ); } return ( DirectBufferedImage.makeDirectImageRGB( width, height ) ); } /** * @return a BufferedImage appropriate for using as a background. It will be the same * size as the overlay with an appropriate format. If one has already been assigned to * this overlay then that is what will be returned, otherwise one is built and returned. * If a new one is built it is NOT assigned as a background image. To do that call * setBackgroundImage() */ public BufferedImage getBackgroundImage() { if ( backgroundImage == null ) return ( getAppropriateImage() ); return ( backgroundImage ); } /** * Sets the background to a solid color. This will be set before every call to * paint() to set the drawing surface. This actually builds a background image matching * the size and format of the overlay and then initializes it to the color. If a background * image already exists then it will be initialized to to the color. It is completely * appropriate to have an alpha component in the color if this is a alpha * capable overlay. In general you should only use background images if this is an * overlay that is called frequently, since you could always paint it inside your * paint() method. This is also designed to support the same background image * used for multiple overlays, like in a scrolling overlay. */ public void setBackgroundColor( Color c ) { if ( backgroundImage == null ) { backgroundImage = getAppropriateImage(); } int[] pixels = new int[ width * height ]; int rgb = c.getRGB(); for ( int i = 0; i < ( width * height ); i++ ) pixels[ i ] = rgb; backgroundImage.setRGB( 0, 0, width, height, pixels, 0, width ); backgroundMode = BACKGROUND_COPY; } /** * Sets the background image to the one specified. It does not have to be the same size as the * overlay but the it should be at least big enough is the backgroundMode is BACKGROUND_COPY, * since that is a straight raster copy. Setting this to null will remove the background * image. */ public void setBackgroundImage( BufferedImage bi ) { backgroundImage = bi; if ( bi == null ) { backgroundMode = BACKGROUND_NONE; } else { backgroundMode = BACKGROUND_COPY; } } /** * Sets the background mode. BACKGROUND_COPY will copy the raster data from the background * into the canvas before paint() is called. BACKGROUND_NONE will cause the background * to be disabled and not used. */ public void setBackgroundMode( int mode ) { backgroundMode = mode; } /** * Constructs an overlay window. * @param width The width of the window in screen space * @param height The height of the window in screen space * @param clipAlpha Should we apply a polygon clip where alpha is zero * @param blendAlpha Should we blend to background if alpha is < 1 * @param owned If this overlay is "owned" then no behavior will be built to update it since * it is assumed that the owner will handle the repaints. Also it will not be * attached to the view transform since it is assumed the owner will do that. * @param offset * @param readDepthBuffer */ public UIOverlay( int width, int height, boolean clipAlpha, boolean blendAlpha, boolean owned, float offset, boolean readDepthBuffer ) { this.owned = owned; this.width = width; this.height = height; //this.blendAlpha = false; this.blendAlpha = blendAlpha; //this.clipAlpha = false; this.clipAlpha = clipAlpha; this.canvas = getAppropriateImage(); // define the rendering attributes used by all sub-overlays this.ra = new RenderingAttributes(); if ( clipAlpha ) { ra.setAlphaTestFunction( RenderingAttributes.NOT_EQUAL ); ra.setAlphaTestValue( 0 ); } ra.setDepthBufferEnabled( false ); ra.setDepthBufferWriteEnabled( false ); /* ra.setDepthBufferEnable( readDepthBuffer ); ra.setDepthBufferWriteEnable( true ); */ ra.setIgnoreVertexColors( true ); //ra.setVisible( true ); // define the polygon attributes for all the sub-overlays pa = new PolygonAttributes(); pa.setBackFaceNormalFlip( false ); pa.setFaceCullMode( FaceCullMode.NONE ); pa.setDrawMode( DrawMode.FILL ); pa.setPolygonOffsetFactor( offset ); // define the texture attributes for all the sub-overlays ta = new TextureAttributes(); ta.setTextureMode( TextureAttributes.REPLACE ); ta.setPerspectiveCorrectionMode( TextureAttributes.NICEST ); // if this needs to support transparancy set up the blend if ( clipAlpha || blendAlpha ) { tra = new TransparencyAttributes( TransparencyAttributes.BLENDED, 1.0f ); ta.setTextureBlendColor( new Colorf( 0f, 0f, 0f, 1f ) ); } // define the branch group where we are putting all the sub-overlays consoleBG = new Group(); // now we need to calculate the sub-overlays needed for the overlay X3DLog.debug( "Overlay : ", width, "x", height ); createSubOverlays(); // attach the behavior to the branchgroup initialize(); mutex = new Object(); } public UIOverlay( int width, int height, boolean clipAlpha, boolean blendAlpha, boolean owned ) { this( width, height, clipAlpha, blendAlpha, owned, 0.0f, true ); } public UIOverlay( int width, int height, boolean clipAlpha, boolean blendAlpha ) { this( width, height, clipAlpha, blendAlpha, false ); } }