espresso3d.engine.window.viewport.E3DViewport.java Source code

Java tutorial

Introduction

Here is the source code for espresso3d.engine.window.viewport.E3DViewport.java

Source

/*
 * Created on Oct 21, 2004
 *
     
  Copyright 2004 Curtis Moxley
  This file is part of Espresso3D.
     
  Espresso3D is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
Espresso3D 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 Lesser General Public License for more details.
    
You should have received a copy of the GNU Lesser General Public License
along with Espresso3D.  If not, see <http://www.gnu.org/licenses/>.
 */
package espresso3d.engine.window.viewport;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;

import espresso3d.engine.E3DEngine;
import espresso3d.engine.lowlevel.geometry.E3DQuad;
import espresso3d.engine.lowlevel.vector.E3DVector2F;
import espresso3d.engine.lowlevel.vector.E3DVector2I;
import espresso3d.engine.lowlevel.vector.E3DVector3F;
import espresso3d.engine.renderer.E3DExternalRenderable;
import espresso3d.engine.renderer.base.E3DRenderMode;
import espresso3d.engine.renderer.base.E3DRenderable;
import espresso3d.engine.renderer.rendertree.E3DRenderTree;
import espresso3d.engine.window.E3DWindow;
import espresso3d.engine.window.viewport.image.E3DImage;
import espresso3d.engine.window.viewport.text.E3DViewportPrinter;
import espresso3d.engine.world.E3DWorld;
import espresso3d.engine.world.sector.actor.E3DActor;

/**
 * @author Curt
 *
 * A viewport is the region that defines where in the window to perform rendering, and what world to render in it
 * Viewports can be switched to either perspective or orthographic rendering modes.  They default
 * in a perspective mode.
 */
public class E3DViewport extends E3DRenderable {
    /**
     * If a viewports rendermode is something other than default,
     *  that rendermode will overwrite any other rendermode for objects in the scene.
     * For example, if it the viewport rendermode is switched to RENDERMODE_WIREFRAME,
     *  all objects will be rendered as a wireframe object
     * 
     * This defaults to RENDERMODE_DEFAULT meaning each object can have its own rendermode
     */
    public static final int RENDERMODE_DEFAULT = -1;

    /**
      * Orthographic mode for the viewport.  This is used for comparing with getMode if necessary, nto for setting the mode
      */
    public static final int VIEWPORT_MODE_ORTHOGRAPHIC = 0;
    /**
     * Perspectve mode for the viewport.  This is used for comparing with getMode if necessary, not for setting the mode
     */
    public static final int VIEWPORT_MODE_PERSPECTIVE = 1;

    private int mode; //can be ortho of perspective

    private String viewportID;

    private E3DWindow window;

    //This is the world that the viewport will render
    private E3DWorld world;
    private E3DActor cameraActor;

    private int x = 0, y = 0;
    private int width, height;

    //perspective
    private double fovY = 45.0, farClipPlane = 1000.0, nearClipPlane = 0.1;

    //orthographic
    private double left = 10.0, right = 10.0, top = 10.0, bottom = 10.0;
    private double orthoZoom;

    //When this is true, the perspective will be re set by calling gluPerspective.
    //A boolean is kept to keep this call to a minimum.
    private boolean perspectiveChanged = true;

    //For projecting/unprojecting
    private FloatBuffer modelView;
    private FloatBuffer projView;
    private IntBuffer intView;
    private FloatBuffer winPos;

    //For the new gluLookAt (reimp)
    //   private static FloatBuffer lookAtBuffer;

    private boolean needViewArrayRecalc = true;

    //Keep track of where the camera was last looking.  If it has changed, we need to
    // recalc the view matrices for projection/unprojection
    private E3DVector3F lastPosition = new E3DVector3F(0.0, 0.0, 0.0);
    private E3DVector3F lastForward = new E3DVector3F(0.0, 0.0, 0.0);
    private E3DVector3F lastUp = new E3DVector3F(0.0, 0.0, 0.0);

    // Viewport Printer for 2D fonts and text
    private E3DViewportPrinter viewportPrinter;

    private HashMap imageMap;
    private E3DRenderTree renderTree;
    //   private E3DSortedRenderableMap sortedImageMap;

    private ArrayList externalRenderables;

    /**
    * This is used to create a perspective viewport
    * @param engine The engine the viewport will be part of
    * @param x The x coordinate of the bottom left corner of the viewport
    * @param y The y coordinate of the bottom left corner of the viewport
    * @param width Width is how many pixels from X, NOT from 0
    * @param height Height is how many pixels from Y, NOT from 0
    * @param viewportID
    */
    public E3DViewport(E3DEngine engine, int x, int y, int width, int height, String viewportID) {
        super(engine);
        setX(x);
        setY(y);
        setWidth(width);
        setHeight(height);
        setViewportID(viewportID);
        setMode(VIEWPORT_MODE_PERSPECTIVE); //default perspective
        viewportPrinter = new E3DViewportPrinter(engine, this);

        lastPosition = new E3DVector3F(0, 0, 0);

        modelView = BufferUtils.createFloatBuffer(16);
        projView = BufferUtils.createFloatBuffer(16);

        intView = BufferUtils.createIntBuffer(4);
        winPos = BufferUtils.createFloatBuffer(3);

        //Images go on two maps
        imageMap = new HashMap();
        renderTree = new E3DRenderTree(engine);

        setRenderMode(new E3DRenderMode(engine, RENDERMODE_DEFAULT));

        this.externalRenderables = new ArrayList();

        needViewArrayRecalc = true;
    }

    /**
     * @return Returns the height.
     */
    public int getHeight() {
        return height;
    }

    /**
     * @param height The height of the viewport.  This is not a coordinate, it is how high to make it from y.  
     * Remember, Y gets larger as it goes up the screen
     */
    public void setHeight(int height) {
        this.height = height;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * @return Returns the width.
     */
    public int getWidth() {
        return width;
    }

    /**
     * @param width The width of the viewport.  This is not a coordinate, it is how wide to make it from x
     */
    public void setWidth(int width) {
        this.width = width;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * @return Returns the x coordinate of the bottom left corner of the viewport
     */
    public int getX() {
        return x;
    }

    /**
     * Sets the x coordinate of the bottom left corner of the viewport.
     * The bottom left corner is 0, and X gets larger as it mvoes right
     * @param x 
     */
    public void setX(int x) {
        this.x = x;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * @return Returns the y coordinate of the bottom left corner of the viewport.  
     * The upper bottom corner is 0, and Y gets larger as it goes up the screen
     */
    public int getY() {
        return y;
    }

    /**
     * Sets the y coordinate of the bottom left corner of the viewport
     * @param y 
     */
    public void setY(int y) {
        this.y = y;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * @return Returns the farClipPlane.  This is how far the scene will be drawn away from the cameara.  Defaults to 100
     */
    public double getFarClipPlane() {
        return farClipPlane;
    }

    /**
     * Sets the far clipping plane.  The large the value, the farther away you will be able to see. However, too large of 
     * a value can hurt performance.
     * @param farClipPlane The farClipPlane to set.
     */
    public void setFarClipPlane(double farClipPlane) {
        this.farClipPlane = farClipPlane;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * @return Returns the fovY.  This is how many degrees you can see in the Y dir
     */
    public double getFovY() {
        return fovY;
    }

    /**
     * Sets how many degress you can see in the Y dir.
     * @param fovY The fovY to set.
     */
    public void setFovY(double fovY) {
        this.fovY = fovY;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * @return Returns the nearClipPlane.
     */
    public double getNearClipPlane() {
        return nearClipPlane;
    }

    /**
     * Sets the near clipping plane.  The smaller the value, the closer you will be able to see objects
     * to the camera.Defaults to 0.1;
     * @param nearClipPlane The nearClipPlane to set.
     */
    public void setNearClipPlane(double nearClipPlane) {
        if (nearClipPlane <= 0.0) //can't be less than/equal 0.0 or project/unproject can have issues
            this.nearClipPlane = 0.0001;
        else
            this.nearClipPlane = nearClipPlane;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * This will automatically be called by the engine.  Users needn't worry about this.
     * Sets the perspective to the values stored in this viewport.  This keeps track of whether
     * the values have changed or not, so it will keep the GL calls to a minimum (only set the perspective
     * if something has actually changed).
     *
     */
    private void setPerspective() {
        if (perspectiveChanged) {
            GL11.glMatrixMode(GL11.GL_PROJECTION); // Select The Projection Matrix
            GL11.glLoadIdentity(); // Reset The Projection Matrix

            if (mode == VIEWPORT_MODE_PERSPECTIVE) {
                // Calculate The Aspect Ratio Of The Window
                GLU.gluPerspective((float) fovY, (float) ((float) width / (float) height), (float) nearClipPlane,
                        (float) farClipPlane);
            } else if (mode == VIEWPORT_MODE_ORTHOGRAPHIC)
                GL11.glOrtho(left * orthoZoom, right * orthoZoom, bottom * orthoZoom, top * orthoZoom,
                        nearClipPlane, farClipPlane); //todo: this needs to center around the camera.  Need to determin whether to use X/Y, Y/Z, or X/Z to center on

            recalcProjectionViewMatrix();

            perspectiveChanged = false;
        }

        GL11.glMatrixMode(GL11.GL_MODELVIEW); // Select The Projection Matrix
        GL11.glLoadIdentity(); // Reset The Modelview Matrix

        GL11.glDepthMask(true); //Re-enable this.  IF particles turn this off, it has to be turned on to be able to clear the depth buffer
        GL11.glEnable(GL11.GL_DEPTH_TEST);
        GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer

        GLU.gluLookAt((float) cameraActor.getOrientation().getPosition().getX(),
                (float) cameraActor.getOrientation().getPosition().getY(),
                (float) cameraActor.getOrientation().getPosition().getZ(),
                ((float) (cameraActor.getOrientation().getPosition().getX()
                        + cameraActor.getOrientation().getForward().getX())),
                ((float) (cameraActor.getOrientation().getPosition().getY()
                        + cameraActor.getOrientation().getForward().getY())),
                ((float) (cameraActor.getOrientation().getPosition().getZ()
                        + cameraActor.getOrientation().getForward().getZ())),
                (float) cameraActor.getOrientation().getUp().getX(),
                (float) cameraActor.getOrientation().getUp().getY(),
                (float) cameraActor.getOrientation().getUp().getZ());

        //If something has changed camera wise, we need to recalculate the viewport matrix 
        // the next time something asks for a projection or unprojection
        if (!cameraActor.getOrientation().getPosition().equals(lastPosition)
                || !cameraActor.getOrientation().getForward().equals(lastForward)
                || !cameraActor.getOrientation().getUp().equals(lastUp)) {
            lastPosition.set(cameraActor.getOrientation().getPosition());
            lastForward.set(cameraActor.getOrientation().getForward());
            lastUp.set(cameraActor.getOrientation().getUp());
            needViewArrayRecalc = true;
        }
    }

    public void switchToViewport() {
        //        GL11.glMatrixMode(GL11.GL_PROJECTION); // Select The Projection Matrix
        GL11.glViewport(x, y, width, height);
        setPerspective();
    }

    /**
     * @return Returns the world that the viewport needs to display
     */
    public E3DWorld getWorld() {
        return world;
    }

    /**
     * @param world The world to set for the viewport to display. This can be null if it shouldn't render a world.
     */
    public void setWorld(E3DWorld world) {
        this.world = world;
    }

    public String getViewportID() {
        return viewportID;
    }

    private void setViewportID(String viewportID) {
        this.viewportID = viewportID;
    }

    /**
     * This is the actor that the viewport uses as the camera.  If this is
      * the primarySoundViewport, the listener position will automatically
      * be set to match this actor.
     * @param cameraActor
     */
    public void setCameraActor(E3DActor cameraActor) {
        this.cameraActor = cameraActor;
        if (getEngine().getPrimarySoundViewport() == this)
            getEngine().getSoundHandler().setListener(cameraActor);

        needViewArrayRecalc = true;
        //   perspectiveChanged = true;
    }

    /**
     * Gets the actor the camera is using as its camera
     * @return
     */
    public E3DActor getCameraActor() {
        return cameraActor;
    }

    /**
     * Gets what mode the viewport is in.  Orthographic or perspective
     * @return
     */
    public int getMode() {
        return mode;
    }

    /**
     * set what mode this viewport is in (ortho or perspective)
     * @param mode
     */
    private void setMode(int mode) {
        this.mode = mode;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * Switch this viewport to orthographic rendering.  Ortho rendering doesn't give the illusion of perspective.
     * 
     * @param left How far left to render of the viewport's camera actor
     * @param right How right left to render of the viewport's camera actor
     * @param top How far above to render of the viewport's camera actor
     * @param bottom How far below to render of the viewport's camera actor
     * @param near How close of objects to render by the camera
     * @param far How far away to render objects from the camera
     */
    public void switchToOrthographicMode(double left, double right, double top, double bottom, double near,
            double far) {
        setMode(VIEWPORT_MODE_ORTHOGRAPHIC);
        setLeft(left);
        setRight(right);
        setTop(top);
        setBottom(bottom);
        setNearClipPlane(near);
        setFarClipPlane(far);
        setOrthoZoom(1);

        needViewArrayRecalc = true;
    }

    /**
     * Switch this viewport to perspective rendering.  Perspective is a standard 3d rendering that simulates what our eyes see.
     * 
     * @param fovY  The field of view angle in degress in the Y direciton. 45 is a standard value.  Too high makes things look fisheyed.
     * @param farClipPlane This specifies the maximum distance that objects can be from the camera and still be rendered
     * @param nearClipPlane Specifies the minimum distance that objects can be from the camera and still be rendered
     */
    public void switchToPerspectiveMode(double fovY, double farClipPlane, double nearClipPlane) {
        setMode(VIEWPORT_MODE_PERSPECTIVE);
        setFovY(fovY);
        setFarClipPlane(farClipPlane);
        setNearClipPlane(nearClipPlane);

        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * set all the boundaries for the ortho view mode in one method
     * @param left
     * @param right
     * @param top
     * @param bottom
     * @param near
     * @param far
     */
    public void setOrthoBounds(double left, double right, double top, double bottom, double near, double far) {
        setLeft(left);
        setRight(right);
        setTop(top);
        setBottom(bottom);
        setNearClipPlane(near);
        setFarClipPlane(far);

        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * This makes the current ortho view zoom out(positive # greater than 1), 
     * or zoom in (positive number > 0.0 and < 1.0).  1 will reset it to the original zoom.
     * Negative numbers and 0 are not allowed and will be converted to 1
     * The actual ortho bounds are not modified, this is a scalar multiplied to them at runtime.
     * @param zoomAmt
     */
    public void setOrthoZoom(double zoomScalar) {
        if (zoomScalar <= 0)
            zoomScalar = 1;
        this.orthoZoom = zoomScalar;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * Gets what the current ortho zoom factor is
     */
    public double getOrthoZoom() {
        return orthoZoom;
    }

    /**
     * How far below the camera to render in Ortho Mode
     * @return
     */
    public double getBottom() {
        return bottom;
    }

    /**
     * Set how far below the camera to render in Ortho Mode
     * @return
     */
    public void setBottom(double bottom) {
        this.bottom = bottom;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * How far left of the camera to render in Ortho Mode
     * @return
     */
    public double getLeft() {
        return left;
    }

    /**
     * Set how far left of the camera to render in Ortho Mode
     * @param left
     */
    public void setLeft(double left) {
        this.left = left;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * How far right of the camera to render in Ortho Mode
     * @param left
     */
    public double getRight() {
        return right;
    }

    /**
     * Set how far right of the camera to render in Ortho Mode
     * @param left
     */
    public void setRight(double right) {
        this.right = right;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    /**
     * How far above the camera to render in Ortho Mode
     * @param left
     */
    public double getTop() {
        return top;
    }

    /**
     * Set how far above the camera to render in Ortho Mode
     * @param left
     */
    public void setTop(double top) {
        this.top = top;
        perspectiveChanged = true;
        needViewArrayRecalc = true;
    }

    public E3DViewportPrinter getViewportPrinter() {
        return viewportPrinter;
    }

    public void setViewportPrinter(E3DViewportPrinter viewportPrinter) {
        this.viewportPrinter = viewportPrinter;
    }

    /**
     * Convert a view matrix float buffer into 2D float array
     * @param fb
     * @return
     */
    private static float[][] getFloatBufferAs2DArray(FloatBuffer fb) {
        float[] array = new float[16];

        fb.get(array);

        fb = (FloatBuffer) fb.rewind();
        float[][] fa = new float[4][4];

        int col = 0;
        for (int i = 0; i < array.length; i++) {
            if (i != 0 && i % 4 == 0)
                col++;

            fa[col][i % 4] = array[i];
        }

        return fa;
    }

    /**
     * Convert a viewport matrix float buffer into 2D int array
     * @param fb
     * @return
     */
    //    private static int[] getIntBufferAsArray(IntBuffer ib)
    //    {
    //        int[] ia = new int[4];
    //        
    //        ib.get(ia);
    //        
    //        return ia;
    //    }   
    //    

    public void recalcProjectionViewMatrix() {
        projView.clear();
        GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projView); //TODO: only do this on perspective change
    }

    public void recalcModelViewMatrix() {
        modelView.clear();
        GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelView); //TODO: grab right out of the gluLookat
    }

    public void recalcViewportMatrix() {
        intView.put(0, x);
        intView.put(1, y);
        intView.put(2, width);
        intView.put(3, height);
    }

    private void recalcViewMatrices() {
        if (needViewArrayRecalc) {
            recalcProjectionViewMatrix();
            recalcModelViewMatrix();
            recalcViewportMatrix();
            needViewArrayRecalc = false;
        }
    }

    /**
     * Projecting a point converts a point in 3D worldspace to 
     * 2D screen coordinates (x, y).  Z of the projected point
     * is a value between 0 and 1 which is where its at between
     * the near and far clip plane
     * @param point
     * @return
     */
    public E3DVector3F projectPoint(E3DVector3F point3D) {
        recalcViewMatrices();

        GLU.gluProject((float) point3D.getX(), (float) point3D.getY(), (float) point3D.getZ(), modelView, projView,
                intView, winPos);

        return new E3DVector3F(winPos.get(0), winPos.get(1), winPos.get(2));
    }

    /**
     * Projecting a point converts a point in 3D worldspace to 
     * 2D screen coordinates (x, y).  Z, or where its at between the near
     * and far clip plane is ignored
     * @param point3D
     * @return
     */
    public E3DVector2F projectPointIgnoreZ(E3DVector3F point3D) {
        recalcViewMatrices();

        GLU.gluProject((float) point3D.getX(), (float) point3D.getY(), (float) point3D.getZ(), modelView, projView,
                intView, winPos);

        return new E3DVector2F(winPos.get(0), winPos.get(1));
    }

    /**
     * Unprojecting takes a 2D point that is in screen coordinate space (x, y)
     * and returns its 3d counterpart in world corodinate space. Z in the point
     * passed in gives the position between the near and far clip plane (value between 0 and 1)
     * 
     * @param point2D
     * @return
     */
    public E3DVector3F unprojectPoint(E3DVector3F point2D) {
        recalcViewMatrices();
        GLU.gluUnProject((int) point2D.getX(), (int) point2D.getY(), (float) point2D.getZ(), modelView, projView,
                intView, winPos);

        //        System.out.println("Unprojecting: " + point2D + " TO " + new E3DVector3F(projArray[0], projArray[1], projArray[2]));

        return new E3DVector3F(winPos.get(0), winPos.get(1), winPos.get(2));
    }

    public HashMap getImageMap() {
        return imageMap;
    }

    //    public void setImageMap(E3DSortedRenderableMap imageMap) {
    //        this.imageMap = imageMap;
    //    }
    /**
     * Add a 2D image to the viewport
     * @param image
     */
    public void addImage(E3DImage image) {
        image.setViewport(this);
        imageMap.put(image.getImageID(), image);
        renderTree.getImageHandler().add(image);
        //        sortedImageMap.addObject(image);
    }

    /**
     * Remove a 2D image from the viewport by imageID
     * @param imageID
     */
    public void removeImage(String imageID) {
        if (imageMap.containsKey(imageID)) {
            E3DImage image = (E3DImage) imageMap.get(imageID);
            renderTree.getImageHandler().remove(image);
            //            sortedImageMap.removeObject((E3DImage)imageMap.get(imageID));
            imageMap.remove(imageID);
        }
    }

    public void render() {
        //Render viewport external renderables
        renderExternalRenderables();

        GL11.glPushMatrix();
        GL11.glLoadIdentity();
        recalcModelViewMatrix(); //loaded identity, force the recalc for unprojection: TODO: Consider making a stack of these as well??

        //Render 2D Images in the viewport
        updateImages();
        //         getRenderTree().render();

        //Render 2D Font text
        getViewportPrinter().render(); //Put font on top of any image

        //            getRenderTree().render();

        GL11.glPopMatrix();
        recalcModelViewMatrix(); //put it back to the way it should be for everything else rendered
    }

    private void renderExternalRenderables() {
        E3DExternalRenderable renderable = null;
        for (int i = 0; i < externalRenderables.size(); i++) {
            renderable = (E3DExternalRenderable) externalRenderables.get(i);
            if (!renderable.update()) {
                externalRenderables.remove(i);
                i--;
            } else {
                GL11.glPushMatrix(); //Each external renderable should be on its own stack so it can make GL calls and not affect other things
                renderable.getRenderable().render();
                GL11.glPopMatrix();
            }
        }
    }

    private void updateImages() {
        if (imageMap == null || imageMap.size() <= 0)
            return;

        E3DImage image = null;
        double lastFrameTime = getEngine().getFpsTimer().getLastUpdateTimeSeconds();

        //Grab all the messages and sort the symbol quads by textureID for efficient rendering
        Iterator it = imageMap.entrySet().iterator();
        Map.Entry entry = null;
        while (it.hasNext()) {
            entry = (Map.Entry) it.next();
            image = (E3DImage) entry.getValue();

            if (!image.update(lastFrameTime)) {
                it.remove();
                renderTree.getImageHandler().remove(image);
            }
        }
        renderTree.render();
    }

    /**
     * @return Returns the externalRenderables list.  This is private to the negine
     */
    private ArrayList getExternalRenderables() {
        return externalRenderables;
    }

    /**
     * This adds an E3DRenderable item to the ExternalRenderable list.  
     *   All E3DRenderables in this list will be rendered after the main
     *  rendering hierarchy is rendered (world, actors, particles, etc) but before any 2D static images or text (so the text/images will be on top of external renderables)
     * Each item on the list will be rendered for the # of frames passed in 
     * This is very useful for debugging (adding CMLines to the rendering loop to see direction vectors and things)
     * It could even potentially be used to plug in your own rendering algorithm.
     * @param renderableItem An E3DRenderable item that you want to render outside of the optimized rendering loop.
     * @param timeoutFrames How many frames to render this renderable for or -1 for infinity
     */
    public void addExternalRenderable(E3DRenderable renderableItem, int timeoutFrames) {
        getExternalRenderables().add(new E3DExternalRenderable(renderableItem, timeoutFrames));
    }

    /**
     * Remove an external renderable from the viewport even if its life hasn't expired
     * @param renderableItem
     */
    public void removeExternalRenderable(E3DRenderable renderableItem) {
        getExternalRenderables().remove(renderableItem);
    }

    public FloatBuffer getModelView() {
        return modelView;
    }

    public E3DRenderTree getRenderTree() {
        return renderTree;
    }

    public E3DWindow getWindow() {
        return window;
    }

    public void setWindow(E3DWindow window) {
        this.window = window;
    }

    public E3DQuad getFixedSizePositionedQuad(E3DVector2I min, E3DVector2I size, double nearZ) {
        return getFixedSizePositionedQuad(null, min, size, nearZ);
    }

    /**
    * Returns a quad in 3D space that is positioned correctly to be rendered in the correct position for 2D space
    * Usually used only by the engine.
    */
    public E3DQuad getFixedSizePositionedQuad(E3DQuad quad, E3DVector2I position, E3DVector2I size, double nearZ) {
        if (quad == null)
            quad = new E3DQuad(getEngine());
        else
            quad = new E3DQuad(quad);

        E3DVector3F unprojA, unprojB, unprojC, unprojD;

        //bottom left
        unprojA = unprojectPoint(new E3DVector3F(position.getX(), position.getY(), nearZ));
        //top left
        unprojB = unprojectPoint(new E3DVector3F(position.getX(), position.getY() + size.getY(), nearZ));
        //top right
        unprojC = unprojectPoint(
                new E3DVector3F(position.getX() + size.getX(), position.getY() + size.getY(), nearZ));
        //bottom right
        unprojD = unprojectPoint(new E3DVector3F(position.getX() + size.getX(), position.getY(), nearZ));

        quad.setVertexPos(unprojA, unprojB, unprojC, unprojD);

        return quad;
    }

    /**
    * @param viewport Viewport this takes place in
    * @param position x,y position for the quad to start in the viewport
    * @param sizePercentage x is width, y is height.  Both are whole percentages : ie 50.00 is 50%
    * @return
    *  Quad that will appear in 3D space what how you specified it in 2D space
    */
    public E3DQuad getVariableSizePositionedQuad(E3DVector2I position, E3DVector2F sizePercentage, double nearZ) {
        return getVariableSizePositionedQuad(null, position, sizePercentage, nearZ);
    }

    /**
    * 
    * @param viewport Viewport this takes place in
    * @param quad A quad if we want to make a copy of all its values (texture coords, etc), or null to get a new quad
    * @param position x,y position for the quad to start in the viewport
    * @param sizePercentage x is width, y is height.  Both are whole percentages : ie 50.00 is 50%
    * @return
    *  Quad that will appear in 3D space what how you specified it in 2D space
    */
    public E3DQuad getVariableSizePositionedQuad(E3DQuad quad, E3DVector2I position, E3DVector2F sizePercentage,
            double nearZ) {
        if (quad == null)
            quad = new E3DQuad(getEngine());
        else
            quad = new E3DQuad(quad);

        E3DVector3F unprojA, unprojB, unprojC, unprojD;

        //bottom left
        unprojA = unprojectPoint(new E3DVector3F(position.getX(), position.getY(), nearZ));
        //top left
        unprojB = unprojectPoint(new E3DVector3F(position.getX(),
                position.getY() + (getHeight() * sizePercentage.getY() * 0.01), nearZ));
        //top right
        unprojC = unprojectPoint(new E3DVector3F(position.getX() + (getWidth() * sizePercentage.getY() * .01),
                position.getY() + (getHeight() * sizePercentage.getY() * 0.01), nearZ));
        //bottom right
        unprojD = unprojectPoint(new E3DVector3F(position.getX() + (getWidth() * sizePercentage.getY() * .01),
                position.getY(), nearZ));

        quad.setVertexPos(unprojD, unprojC, unprojB, unprojA); //reverse so backface culling works

        return quad;
    }
}