/**
 * This package provides classes to facilitate the handling of opengl textures, glsl shaders and 
 * off-screen rendering in Processing.
 * @author Andres Colubri
 * @author Ilias Bergstrom (http://www.onar3d.com/): Multisampled FBO option.
 * @version 0.9.4.1
 *
 * Copyright (c) 2008 Andres Colubri
 *
 * This source 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 2 of the License, or
 * (at your option) any later version.
 * 
 * This code 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.
 * 
 * A copy of the GNU General Public License is available on the World
 * Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also
 * obtain it by writing to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

package codeanticode.glgraphics;

import processing.core.*;
import processing.opengl.*;
import javax.media.opengl.*;

/**
 * This class implements an opengl renderer for off-screen rendering to texture.
 */
public class GLGraphicsOffScreen extends PGraphicsOpenGL implements GLConstants {
  // Framebuffer object and textures needed for the color, depth and stencil
  // buffers.
  protected GLFramebufferObject multisampleFBO;
  protected GLFramebufferObject FBO;
  protected int[] colorBufferMulti = null;
  protected int[] depthStencilBuffer = null;
  protected GLTexture colorTex;
  protected GLState glstate;
  protected GLCapabilities glcaps;

  protected int multisampleLevel = 4;
  protected boolean multisampleEnabled;
  protected GLTextureParameters colorTexParams;

  protected boolean glMode = false;
  protected float[] modelviewTM;

  protected boolean blend;
  protected boolean blend0;
  protected int blendMode;

  // Light-related variables, taken out from PGraphics3D.

  /**
   * Maximum lights by default is 8, the minimum defined by OpenGL.
   */
  public static final int MAX_LIGHTS_GL = 8;

  public boolean lightsGL = false;  
  
  public int lightCountGL = 0;

  /** Light types */
  public int[] lightTypeGL;

  /** Light positions */
  public float[][] lightPositionGL;

  /** Light direction (normalized vector) */
  public float[][] lightNormalGL;

  /** Light falloff */
  public float[] lightFalloffConstantGL;
  public float[] lightFalloffLinearGL;
  public float[] lightFalloffQuadraticGL;

  /** Light spot angle */
  public float[] lightSpotAngleGL;

  /** Cosine of light spot angle */
  public float[] lightSpotAngleCosGL;

  /** Light spot concentration */
  public float[] lightSpotConcentrationGL;

  /**
   * Diffuse colors for lights. For an ambient light, this will hold the ambient
   * color. Internally these are stored as numbers between 0 and 1.
   */
  public float[][] lightDiffuseGL;

  /**
   * Specular colors for lights. Internally these are stored as numbers between
   * 0 and 1.
   */
  public float[][] lightSpecularGL;

  /** Current specular color for lighting */
  public float[] currentLightSpecularGL;

  /** Current light falloff */
  public float currentLightFalloffConstantGL;
  public float currentLightFalloffLinearGL;
  public float currentLightFalloffQuadraticGL;

  public float[] zeroBufferGL = { 0.0f, 0.0f, 0.0f, 0.0f };

  public int saveLightCount;

  protected GLGraphics mainRenderer;
    
  /**
   * Creates an instance of GLGraphicsOffScreen with specified size, with
   * mulisampling disabled.
   * 
   * @param parent
   *          PApplet
   * @param iwidth
   *          int
   * @param iheight
   *          int
   */
  public GLGraphicsOffScreen(PApplet parent, int iwidth, int iheight) {
    super();
    multisampleEnabled = false;
    colorTexParams = new GLTextureParameters();
    multisampleLevel = 4;
    setParent(parent);
    setSize(iwidth, iheight);
  }

  /**
   * Creates an instance of GLGraphicsOffScreen with specified size and
   * enabled/disabled multisampling, according to the value of the argument
   * multi. If multisampling is enabled the multisampling level is set by
   * default to 4x.
   * 
   * @param parent
   *          PApplet
   * @param iwidth
   *          int
   * @param iheight
   *          int
   * @param multi
   *          int
   */
  public GLGraphicsOffScreen(PApplet parent, int iwidth, int iheight,
      boolean multi) {
    super();
    multisampleEnabled = multi;
    colorTexParams = new GLTextureParameters();
    multisampleLevel = 4;
    setParent(parent);
    setSize(iwidth, iheight);
  }

  /**
   * Creates an instance of GLGraphicsOffScreen with specified size and
   * enabled/disabled multisampling and multisampling level, according to the
   * values of the arguments multi and level.
   * 
   * @param parent
   *          PApplet
   * @param iwidth
   *          int
   * @param iheight
   *          int
   * @param multi
   *          int
   * @param level
   *          int
   */
  public GLGraphicsOffScreen(PApplet parent, int iwidth, int iheight,
      boolean multi, int level) {
    super();
    multisampleEnabled = multi;
    colorTexParams = new GLTextureParameters();
    multisampleLevel = level;
    setParent(parent);
    setSize(iwidth, iheight);
  }

  /**
   * Creates an instance of GLGraphicsOffScreen with specified size and
   * parameters for the destination texture. Multisampling is disabled.
   * 
   * @param parent
   *          PApplet
   * @param iwidth
   *          int
   * @param iheight
   *          int
   * @param params
   *          GLTextureParameters
   */
  public GLGraphicsOffScreen(PApplet parent, int iwidth, int iheight,
      GLTextureParameters params) {
    super();
    multisampleEnabled = false;
    colorTexParams = params;
    multisampleLevel = 4;
    setParent(parent);
    setSize(iwidth, iheight);
  }

  /**
   * Creates an instance of GLGraphicsOffScreen with specified size and
   * parameters for the destination texture. Multisampling is enabled/disabled
   * according to the value of the argument multi. If multisampling is enabled,
   * the multisampling level is set by default to 4x.
   * 
   * @param parent
   *          PApplet
   * @param iwidth
   *          int
   * @param iheight
   *          int
   * @param params
   *          GLTextureParameters
   * @param multi
   *          int
   */
  public GLGraphicsOffScreen(PApplet parent, int iwidth, int iheight,
      GLTextureParameters params, boolean multi) {
    super();
    multisampleEnabled = multi;
    colorTexParams = params;
    multisampleLevel = 4;
    setParent(parent);
    setSize(iwidth, iheight);
  }

  /**
   * Creates an instance of GLGraphicsOffScreen with specified size and
   * parameters for the destination texture. Multisampling is enabled/disabled
   * according to the value of the argument multi, and its level is contolled by
   * the argument level.
   * 
   * @param parent
   *          PApplet
   * @param iwidth
   *          int
   * @param iheight
   *          int
   * @param params
   *          GLTextureParameters
   * @param multi
   *          int
   * @param level
   *          int
   */
  public GLGraphicsOffScreen(PApplet parent, int iwidth, int iheight,
      GLTextureParameters params, boolean multi, int level) {
    super();
    multisampleEnabled = multi;
    colorTexParams = params;
    multisampleLevel = level;
    setParent(parent);
    setSize(iwidth, iheight);
  }

  public void setSize(int iwidth, int iheight) {
    super.setSize(iwidth, iheight);

    // init lights (in resize() instead of allocate() b/c needed by opengl)
    lightTypeGL = new int[MAX_LIGHTS_GL];
    lightPositionGL = new float[MAX_LIGHTS_GL][4];
    lightNormalGL = new float[MAX_LIGHTS_GL][4];
    lightDiffuseGL = new float[MAX_LIGHTS_GL][4];
    lightSpecularGL = new float[MAX_LIGHTS_GL][4];
    lightFalloffConstantGL = new float[MAX_LIGHTS_GL];
    lightFalloffLinearGL = new float[MAX_LIGHTS_GL];
    lightFalloffQuadraticGL = new float[MAX_LIGHTS_GL];
    lightSpotAngleGL = new float[MAX_LIGHTS_GL];
    lightSpotAngleCosGL = new float[MAX_LIGHTS_GL];
    lightSpotConcentrationGL = new float[MAX_LIGHTS_GL];
    currentLightSpecularGL = new float[4];
  }

  protected void finalize() throws Throwable {
    if (colorBufferMulti != null && colorBufferMulti[0] != 0) {
      gl.glDeleteRenderbuffersEXT(1, colorBufferMulti, 0);
    }

    if (depthStencilBuffer != null && depthStencilBuffer[0] != 0) {
      gl.glDeleteRenderbuffersEXT(1, depthStencilBuffer, 0);
    }

    super.finalize();
  }

  /**
   * Returns the texture where this offscreen renderer draws to.
   * 
   * @return GLTexture
   */
  public GLTexture getTexture() {
    return colorTex;
  }

  /**
   * Returns the OpenGL capabilities of the drawable associated to this
   * renderer.
   * 
   * @return GLCapabilities
   */
  public GLCapabilities getCapabilities() {
    return glcaps;
  }

  /**
   * Creates a new context for canvas that is shared with the context of
   * GLGraphics.
   * 
   * @param GLCanvas
   *          canvas
   */
  public void shareContext(GLCanvas canvas) {
    if (context != null)
      canvas.createContext(context);
  }

  /**
   * Creates a new context for drawable that is shared with the context of
   * GLGraphics.
   * 
   * @param GLDrawable
   *          drawable
   */
  public void shareContext(GLDrawable drawable) {
    if (context != null)
      drawable.createContext(context);
  }

  public GL beginGL() {
    super.beginGL();
    glMode = true;
    // We need to setup light falloff and specular 
    // coefficients because in beginDraw is done
    // for the parent's lights (non-OpenGL).
    lightFalloff(1, 0, 0);
    lightSpecular(0, 0, 0);    
    return gl;
  }

  public void endGL() {
    glMode = false;
    super.endGL();
  }

  /**
   * Any drawing operation with this rendered must be enclosed between a call to
   * beginDraw() and endDraw().
   */
  public void beginDraw() {
    // System.out.println("  Begin Draw");
    if ((parent.width != width) || (parent.height != height)) {
      // Saving current viewport.
      gl.glPushAttrib(GL.GL_VIEWPORT_BIT);

      // Saving current projection matrix.
      gl.glMatrixMode(GL.GL_PROJECTION);
      gl.glPushMatrix();

      // Setting the appropriate viewport.
      gl.glViewport(0, 0, width, height);
    }

    super.beginDraw();

    gl.glMatrixMode(GL.GL_MODELVIEW);
    gl.glPushMatrix();
    gl.glScalef(1.0f, -1.0f, 1.0f);

    // Directing rendering to the texture...
    if (multisampleEnabled) {
      glstate.pushFramebuffer();
      glstate.setFramebuffer(multisampleFBO);
      gl.glDrawBuffer(GL.GL_COLOR_ATTACHMENT0_EXT);
    } else {
      glstate.pushFramebuffer();
      glstate.setFramebuffer(FBO);
      FBO.setDrawBuffer(colorTex);
    }

    // Clearing Z-buffer to ensure that the new elements are drawn properly.
    gl.glClearColor(0f, 0f, 0f, 0.0f);
    gl.glClear(GL.GL_DEPTH_BUFFER_BIT);

    // Setting the offscreen renderer in the main (GLGraphics) renderer so that
    // calls to transformation routines made to the GLGraphics renderer when
    // the offscreen renderer is enabled are directed to the offscreen renderer.
    if (mainRenderer != null) {
      mainRenderer.setOffScreenRenderer(this);

      // While doing off-screen rendering, the current light configuration of
      // the main renderer is shut-off
      // (but saved so it can restored after endDraw()).
      mainRenderer.saveLights();
      mainRenderer.disableLights();      
    }

    lightsGL = false;
    lightCountGL = 0;
    lightFalloff(1, 0, 0);
    lightSpecular(0, 0, 0);
  }

  /**
   * Any drawing operation with this rendered must be enclosed between a call to
   * beginDraw() and endDraw().
   */
  public void endDraw() {
    if (mainRenderer != null) {
      mainRenderer.setOffScreenRenderer(null);
      mainRenderer.restoreLights();
    }

    super.endDraw();

    // Restoring render to previous framebuffer.
    if (multisampleEnabled) {
      // Then downsample the multisampled to the normal buffer with a blit
      gl.glBindFramebufferEXT(GL.GL_READ_FRAMEBUFFER_EXT, multisampleFBO
          .getFramebufferID()); // source
      gl.glBindFramebufferEXT(GL.GL_DRAW_FRAMEBUFFER_EXT, FBO
          .getFramebufferID()); // dest
      gl.glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height,
          GL.GL_COLOR_BUFFER_BIT, GL.GL_NEAREST);
      glstate.popFramebuffer();
    } else {
      glstate.popFramebuffer();
    }

    if ((parent.width != width) || (parent.height != height)) {
      // Restoring previous GL matrices.
      gl.glMatrixMode(GL.GL_PROJECTION);
      gl.glPopMatrix();

      // Restoring previous viewport.
      gl.glPopAttrib();
    }

    gl.glMatrixMode(GL.GL_MODELVIEW);
    gl.glPopMatrix();

    // System.out.println("  End Draw");
  }

  public void pushMatrix() {
    if (glMode)
      gl.glPushMatrix();
    else
      super.pushMatrix();
  }

  public void popMatrix() {
    if (glMode)
      gl.glPopMatrix();
    else
      super.popMatrix();
  }

  public void translate(float tx, float ty) {
    if (glMode)
      gl.glTranslatef(tx, ty, 0.0f);
    else
      super.translate(tx, ty, 0.0f);
  }

  public void translate(float tx, float ty, float tz) {
    if (glMode)
      gl.glTranslatef(tx, ty, tz);
    else
      super.translate(tx, ty, tz);
  }

  public void rotate(float angle) {
    if (glMode)
      gl.glRotatef(PApplet.degrees(angle), 0.0f, 0.0f, 1.0f);
    else
      super.rotate(angle);
  }

  public void rotateX(float angle) {
    if (glMode)
      gl.glRotatef(PApplet.degrees(angle), 1.0f, 0.0f, 0.0f);
    else
      super.rotateX(angle);
  }

  public void rotateY(float angle) {
    if (glMode)
      gl.glRotatef(PApplet.degrees(angle), 0.0f, 1.0f, 0.0f);
    else
      super.rotateY(angle);
  }

  public void rotateZ(float angle) {
    if (glMode)
      gl.glRotatef(PApplet.degrees(angle), 0.0f, 0.0f, 1.0f);
    else
      super.rotateZ(angle);
  }

  public void rotate(float angle, float vx, float vy, float vz) {
    if (glMode)
      gl.glRotatef(PApplet.degrees(angle), vx, vy, vz);
    else
      super.rotate(angle, vx, vy, vz);
  }

  public void scale(float s) {
    if (glMode)
      gl.glScalef(s, s, s);
    else
      super.scale(s);
  }

  public void scale(float sx, float sy) {
    if (glMode)
      gl.glScalef(sx, sy, 1.0f);
    else
      super.scale(sx, sy);
  }

  public void scale(float x, float y, float z) {
    if (glMode)
      gl.glScalef(x, y, x);
    else
      super.scale(x, y, z);
  }

  public void lights() {
    if (glMode) {
      glEnableLights();

      // need to make sure colorMode is RGB 255 here
      int colorModeSaved = colorMode;
      colorMode = RGB;

      lightFalloff(1, 0, 0);
      lightSpecular(0, 0, 0);

      // No lights by default.
      ambientLight(colorModeX * 0.5f, colorModeY * 0.5f, colorModeZ * 0.5f);
      directionalLight(colorModeX * 0.5f, colorModeY * 0.5f, colorModeZ * 0.5f, 0, 0, -1);

      colorMode = colorModeSaved;
    } else
      super.lights();
  }

  public void saveLights() {
    if (glMode)
      saveLightCount = lightCountGL;
    else
      saveLightCount = lightCount;
  }

  public void restoreLights() {
    if (glMode) {
      lightCountGL = saveLightCount;
      for (int i = 0; i < lightCountGL; i++) {
        glLightEnable(i);
        if (lightTypeGL[i] == AMBIENT) {
          glLightAmbient(i);
          glLightPosition(i);
          glLightFalloff(i);
          glLightNoSpot(i);
          glLightNoDiffuse(i);
          glLightNoSpecular(i);                     
        } else if (lightTypeGL[i] == DIRECTIONAL) {
          glLightNoAmbient(i);
          glLightDirection(i);
          glLightDiffuse(i);
          glLightSpecular(i);
          glLightFalloff(i);
          glLightNoSpot(i);
        } else if (lightTypeGL[i] == POINT) {
          glLightNoAmbient(i);
          glLightPosition(i);
          glLightDiffuse(i);
          glLightSpecular(i);
          glLightFalloff(i);
          glLightNoSpot(i);
        } else if (lightTypeGL[i] == SPOT) {
          glLightNoAmbient(i);
          glLightPosition(i);
          glLightDirection(i);
          glLightDiffuse(i);
          glLightSpecular(i);
          glLightFalloff(i);
          glLightSpotAngle(i);
          glLightSpotConcentration(i);
        }
      }
    } else {
      lightCount = saveLightCount;
    }
  }

  public void noLights() {
    if (glMode) {
      glDisableLights();
      lightCountGL = 0;
    } else
      super.noLights();
  }

  /**
   * Add an ambient light based on the current color mode.
   */
  public void ambientLight(float r, float g, float b) {
    if (glMode)
      ambientLight(r, g, b, 0, 0, 0);
    else
      super.ambientLight(r, g, b, 0, 0, 0);
  }

  /**
   * Add an ambient light based on the current color mode. This version includes
   * an (x, y, z) position for situations where the falloff distance is used.
   */
  public void ambientLight(float r, float g, float b, float x, float y, float z) {
    if (!glMode) {
      super.ambientLight(r, g, b, x, y, z);
      return;
    }

    if (!lightsGL) {
      glEnableLights();
    }
        
    if (lightCountGL == MAX_LIGHTS_GL) {
      throw new RuntimeException("can only create " + MAX_LIGHTS_GL + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuseGL[lightCountGL][0] = calcR;
    lightDiffuseGL[lightCountGL][1] = calcG;
    lightDiffuseGL[lightCountGL][2] = calcB;
    lightDiffuseGL[lightCountGL][3] = 1.0f;

    lightTypeGL[lightCountGL] = AMBIENT;
    lightFalloffConstantGL[lightCountGL] = currentLightFalloffConstantGL;
    lightFalloffLinearGL[lightCountGL] = currentLightFalloffLinearGL;
    lightFalloffQuadraticGL[lightCountGL] = currentLightFalloffQuadraticGL;
    lightPositionGL[lightCountGL][0] = x;
    lightPositionGL[lightCountGL][1] = y;
    lightPositionGL[lightCountGL][2] = z;
    lightPositionGL[lightCountGL][3] = 1.0f;

    glLightEnable(lightCountGL);
    glLightAmbient(lightCountGL);
    glLightPosition(lightCountGL);
    glLightFalloff(lightCountGL);
    glLightNoSpot(lightCountGL);
    glLightNoDiffuse(lightCountGL);
    glLightNoSpecular(lightCountGL);    

    lightCountGL++;
  }

  public void directionalLight(float r, float g, float b, float nx, float ny,
      float nz) {
    if (!glMode) {
      super.directionalLight(r, g, b, nx, ny, nz);
      return;
    }

    if (!lightsGL) {
      glEnableLights();
    }
        
    if (lightCountGL == MAX_LIGHTS_GL) {
      throw new RuntimeException("can only create " + MAX_LIGHTS_GL + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuseGL[lightCountGL][0] = calcR;
    lightDiffuseGL[lightCountGL][1] = calcG;
    lightDiffuseGL[lightCountGL][2] = calcB;
    lightDiffuseGL[lightCountGL][3] = 1.0f;

    lightTypeGL[lightCountGL] = DIRECTIONAL;
    lightFalloffConstantGL[lightCountGL] = currentLightFalloffConstantGL;
    lightFalloffLinearGL[lightCountGL] = currentLightFalloffLinearGL;
    lightFalloffQuadraticGL[lightCountGL] = currentLightFalloffQuadraticGL;
    lightSpecularGL[lightCountGL][0] = currentLightSpecularGL[0];
    lightSpecularGL[lightCountGL][1] = currentLightSpecularGL[1];
    lightSpecularGL[lightCountGL][2] = currentLightSpecularGL[2];
    lightSpecularGL[lightCountGL][3] = currentLightSpecularGL[3];

    float invn = 1.0f / PApplet.dist(0, 0, 0, nx, ny, nz);
    lightNormalGL[lightCountGL][0] = invn * nx;
    lightNormalGL[lightCountGL][1] = invn * ny;
    lightNormalGL[lightCountGL][2] = invn * nz;
    lightNormalGL[lightCountGL][3] = 0.0f;

    glLightEnable(lightCountGL);
    glLightNoAmbient(lightCountGL);
    glLightDirection(lightCountGL);
    glLightDiffuse(lightCountGL);
    glLightSpecular(lightCountGL);
    glLightFalloff(lightCountGL);
    glLightNoSpot(lightCountGL);

    lightCountGL++;
  }

  public void pointLight(float r, float g, float b, float x, float y, float z) {
    if (!glMode) {
      super.pointLight(r, g, b, x, y, z);
      return;
    }

    if (!lightsGL) {
      glEnableLights();
    }
        
    if (lightCountGL == MAX_LIGHTS_GL) {
      throw new RuntimeException("can only create " + MAX_LIGHTS_GL + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuseGL[lightCountGL][0] = calcR;
    lightDiffuseGL[lightCountGL][1] = calcG;
    lightDiffuseGL[lightCountGL][2] = calcB;
    lightDiffuseGL[lightCountGL][3] = 1.0f;

    lightTypeGL[lightCountGL] = POINT;
    lightFalloffConstantGL[lightCountGL] = currentLightFalloffConstantGL;
    lightFalloffLinearGL[lightCountGL] = currentLightFalloffLinearGL;
    lightFalloffQuadraticGL[lightCountGL] = currentLightFalloffQuadraticGL;
    lightSpecularGL[lightCountGL][0] = currentLightSpecularGL[0];
    lightSpecularGL[lightCountGL][1] = currentLightSpecularGL[1];
    lightSpecularGL[lightCountGL][2] = currentLightSpecularGL[2];

    lightPositionGL[lightCountGL][0] = x;
    lightPositionGL[lightCountGL][1] = y;
    lightPositionGL[lightCountGL][2] = z;
    lightPositionGL[lightCountGL][3] = 1.0f;

    glLightEnable(lightCountGL);
    glLightNoAmbient(lightCountGL);
    glLightPosition(lightCountGL);
    glLightDiffuse(lightCountGL);
    glLightSpecular(lightCountGL);
    glLightFalloff(lightCountGL);
    glLightNoSpot(lightCountGL);

    lightCountGL++;
  }

  public void spotLight(float r, float g, float b, float x, float y, float z,
      float nx, float ny, float nz, float angle, float concentration) {
    if (!glMode) {
      super.spotLight(r, g, b, x, y, z, nx, ny, nz, angle, concentration);
      return;
    }

    if (!lightsGL) {
      glEnableLights();
    }
        
    if (lightCountGL == MAX_LIGHTS_GL) {
      throw new RuntimeException("can only create " + MAX_LIGHTS_GL + " lights");
    }
    colorCalc(r, g, b);
    lightDiffuseGL[lightCountGL][0] = calcR;
    lightDiffuseGL[lightCountGL][1] = calcG;
    lightDiffuseGL[lightCountGL][2] = calcB;
    lightDiffuseGL[lightCountGL][3] = 1.0f;

    lightTypeGL[lightCountGL] = SPOT;
    lightFalloffConstantGL[lightCountGL] = currentLightFalloffConstantGL;
    lightFalloffLinearGL[lightCountGL] = currentLightFalloffLinearGL;
    lightFalloffQuadraticGL[lightCountGL] = currentLightFalloffQuadraticGL;
    lightSpecularGL[lightCountGL][0] = currentLightSpecularGL[0];
    lightSpecularGL[lightCountGL][1] = currentLightSpecularGL[1];
    lightSpecularGL[lightCountGL][2] = currentLightSpecularGL[2];

    lightPositionGL[lightCountGL][0] = x;
    lightPositionGL[lightCountGL][1] = y;
    lightPositionGL[lightCountGL][2] = z;
    lightPositionGL[lightCountGL][3] = 1.0f;

    float invn = 1.0f / PApplet.dist(0, 0, 0, nx, ny, nz);
    lightNormalGL[lightCountGL][0] = invn * nx;
    lightNormalGL[lightCountGL][1] = invn * ny;
    lightNormalGL[lightCountGL][2] = invn * nz;
    lightNormalGL[lightCountGL][3] = 0.0f;

    lightSpotAngleGL[lightCountGL] = PApplet.degrees(angle);
    lightSpotAngleCosGL[lightCountGL] = Math.max(0, (float) Math.cos(angle));
    lightSpotConcentrationGL[lightCountGL] = concentration;

    glLightEnable(lightCountGL);
    glLightNoAmbient(lightCountGL);
    glLightPosition(lightCountGL);
    glLightDirection(lightCountGL);
    glLightDiffuse(lightCountGL);
    glLightSpecular(lightCountGL);
    glLightFalloff(lightCountGL);
    glLightSpotAngle(lightCountGL);
    glLightSpotConcentration(lightCountGL);

    lightCountGL++;
  }

  /**
   * Set the light falloff rates for the last light that was created. Default is
   * lightFalloff(1, 0, 0).
   */
  public void lightFalloff(float constant, float linear, float quadratic) {
    if (!glMode) {
      super.lightFalloff(constant, linear, quadratic);
      return;
    }

    currentLightFalloffConstantGL = constant;
    currentLightFalloffLinearGL = linear;
    currentLightFalloffQuadraticGL = quadratic;
  }

  /**
   * Set the specular color of the last light created.
   */
  public void lightSpecular(float x, float y, float z) {
    if (!glMode) {
      super.lightSpecular(x, y, z);
      return;
    }

    colorCalc(x, y, z);
    currentLightSpecularGL[0] = calcR;
    currentLightSpecularGL[1] = calcG;
    currentLightSpecularGL[2] = calcB;
    currentLightSpecularGL[3] = 1.0f;
  }

  private void glEnableLights() {
    lightsGL = true;
    gl.glEnable(GL.GL_LIGHTING);
    gl.glEnable(GL.GL_COLOR_MATERIAL);
    gl.glColorMaterial(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT_AND_DIFFUSE);    
  }

  private void glDisableLights() {
    lightsGL = false;
    gl.glDisable(GL.GL_LIGHTING);    
  }
    
  private void glLightAmbient(int num) {
    gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_AMBIENT, lightDiffuseGL[num], 0);
  }

  private void glLightNoAmbient(int num) {
    gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_AMBIENT, zeroBufferGL, 0);
  }

  private void glLightNoSpot(int num) {
    gl.glLightf(GL.GL_LIGHT0 + num, GL.GL_SPOT_CUTOFF, 180);
    gl.glLightf(GL.GL_LIGHT0 + num, GL.GL_SPOT_EXPONENT, 0);
  }

  private void glLightDiffuse(int num) {
    gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_DIFFUSE, lightDiffuseGL[num], 0);
  }

  private void glLightNoDiffuse(int num) {
    gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_DIFFUSE, zeroBufferGL, 0);
  }
    
  private void glLightDirection(int num) {
    if (lightTypeGL[num] == DIRECTIONAL) {
      // TODO this expects a fourth arg that will be set to 1
      // this is why lightBuffer is length 4,
      // and the [3] element set to 1 in the constructor.
      // however this may be a source of problems since
      // it seems a bit "hack"
      gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_POSITION, lightNormalGL[num], 0);
    } else { // spotlight
      // this one only needs the 3 arg version
      gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_SPOT_DIRECTION,
          lightNormalGL[num], 0);
    }
  }

  private void glLightEnable(int num) {
    gl.glEnable(GL.GL_LIGHT0 + num);
  }

  private void glLightDisable(int num) {
    gl.glDisable(GL.GL_LIGHT0 + num);
  }

  private void glLightFalloff(int num) {
    gl.glLightf(GL.GL_LIGHT0 + num, GL.GL_CONSTANT_ATTENUATION,
        lightFalloffConstantGL[num]);
    gl.glLightf(GL.GL_LIGHT0 + num, GL.GL_LINEAR_ATTENUATION,
        lightFalloffLinearGL[num]);
    gl.glLightf(GL.GL_LIGHT0 + num, GL.GL_QUADRATIC_ATTENUATION,
        lightFalloffQuadraticGL[num]);
  }

  private void glLightPosition(int num) {
    gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_POSITION, lightPositionGL[num], 0);
  }

  private void glLightSpecular(int num) {
    gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_SPECULAR, lightSpecularGL[num], 0);
  }

  private void glLightNoSpecular(int num) {
    gl.glLightfv(GL.GL_LIGHT0 + num, GL.GL_SPECULAR, zeroBufferGL, 0);
  }    
  
  private void glLightSpotAngle(int num) {
    gl.glLightf(GL.GL_LIGHT0 + num, GL.GL_SPOT_CUTOFF, lightSpotAngleGL[num]);
  }

  private void glLightSpotConcentration(int num) {
    gl.glLightf(GL.GL_LIGHT0 + num, GL.GL_SPOT_EXPONENT,
        lightSpotConcentrationGL[num]);
  }

  public void endCamera() {
    super.endCamera();
    if (glMode)
      loadGLModelviewMatrix();
  }

  public void camera() {
    camera(cameraX, cameraY, cameraZ, cameraX, cameraY, 0, 0, 1, 0);
  }

  public void camera(float eyeX, float eyeY, float eyeZ, float centerX,
      float centerY, float centerZ, float upX, float upY, float upZ) {
    super.camera(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
    if (glMode) {
      loadGLModelviewMatrix();
    }
  }

  public void ortho() {
    ortho(0, width, 0, height, -10, 10);
  }

  public void ortho(float left, float right, float bottom, float top,
      float near, float far) {
    // Will call updateProjection in PGraphicsOpenGL, which copies the
    // projection matrix
    // to opengl
    super.ortho(left, right, bottom, top, near, far);
    gl.glMatrixMode(GL.GL_MODELVIEW); // We leave the modelview as the active
                                      // matrix.
  }

  public void perspective() {
    perspective(cameraFOV, cameraAspect, cameraNear, cameraFar);
  }

  public void perspective(float fov, float aspect, float near, float far) {
    super.perspective(fov, aspect, near, far); // Will call frustum()
  }

  public void frustum(float left, float right, float bottom, float top,
      float near, float far) {
    // Will call updateProjection in PGraphicsOpenGL, which copies the
    // projection matrix
    // to opengl
    super.frustum(left, right, bottom, top, near, far);
    gl.glMatrixMode(GL.GL_MODELVIEW); // We leave the modelview as the active
                                      // matrix.
  }

  public void model(GLModel model) {
    model.render();
  }

  public void model(GLModel model, GLModelEffect effect) {
    model.render(effect);
  }

  public void model(GLModel model, int first, int last) {
    model.render(first, last, null);
  }

  public void model(GLModel model, int first, int last, GLModelEffect effect) {
    model.render(first, last, effect);
  }

  // Setup offscreen framebuffer.
  protected void initFramebuffer() {
    int stat;

    gl = context.getGL();

    glstate = new GLState(gl);

    // Creating arrays for FBO and depth&stencil buffer.
    FBO = new GLFramebufferObject(gl);
    depthStencilBuffer = new int[1];
    depthStencilBuffer[0] = 0;

    // Create depth and stencil buffers.
    gl.glGenRenderbuffersEXT(1, depthStencilBuffer, 0);

    // Creating color texture.
    colorTex = new GLTexture(parent, width, height, colorTexParams);
    colorTex.clear(0, 0);

    if (multisampleEnabled) {
      multisampleFBO = new GLFramebufferObject(gl);

      colorBufferMulti = new int[1];
      colorBufferMulti[0] = 0;

      // Creating handle for multisampled color buffer.
      gl.glGenRenderbuffersEXT(1, colorBufferMulti, 0);
      gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, colorBufferMulti[0]); // Binding
                                                                             // render
                                                                             // buffer
      gl.glRenderbufferStorageMultisampleEXT(GL.GL_RENDERBUFFER_EXT,
          multisampleLevel, GL.GL_RGBA8, width, height);

      // Creating handle for multisampled depth buffer.
      gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, depthStencilBuffer[0]); // Binding
                                                                               // depth
                                                                               // buffer
      // Allocating space for multisampled depth buffer
      gl.glRenderbufferStorageMultisampleEXT(GL.GL_RENDERBUFFER_EXT,
          multisampleLevel, GL_DEPTH24_STENCIL8, width, height);

      // Creating handle for multisampled FBO
      glstate.pushFramebuffer();
      glstate.setFramebuffer(multisampleFBO);

      gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT,
          GL.GL_COLOR_ATTACHMENT0_EXT, GL.GL_RENDERBUFFER_EXT,
          colorBufferMulti[0]);
      gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT,
          GL.GL_DEPTH_ATTACHMENT_EXT, GL.GL_RENDERBUFFER_EXT,
          depthStencilBuffer[0]);
      gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT,
          GL.GL_STENCIL_ATTACHMENT_EXT, GL.GL_RENDERBUFFER_EXT,
          depthStencilBuffer[0]);

      glstate.pushFramebuffer();
      glstate.setFramebuffer(FBO);

      gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT,
          GL.GL_COLOR_ATTACHMENT0_EXT, GL.GL_TEXTURE_2D, colorTex
              .getTextureID(), 0);

      stat = gl.glCheckFramebufferStatusEXT(GL.GL_FRAMEBUFFER_EXT);
      GLUtils.printFramebufferError(stat);

      glstate.popFramebuffer();
      glstate.popFramebuffer();
    } else {
      // / Regular, no multisampling, FBO setup.
      gl.glBindRenderbufferEXT(GL.GL_RENDERBUFFER_EXT, depthStencilBuffer[0]);
      gl.glRenderbufferStorageEXT(GL.GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8,
          width, height);

      glstate.pushFramebuffer();
      glstate.setFramebuffer(FBO);

      gl.glFramebufferTexture2DEXT(GL.GL_FRAMEBUFFER_EXT,
          GL.GL_COLOR_ATTACHMENT0_EXT, colorTex.getTextureTarget(), colorTex
              .getTextureID(), 0);
      gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT,
          GL.GL_DEPTH_ATTACHMENT_EXT, GL.GL_RENDERBUFFER_EXT,
          depthStencilBuffer[0]);
      gl.glFramebufferRenderbufferEXT(GL.GL_FRAMEBUFFER_EXT,
          GL.GL_STENCIL_ATTACHMENT_EXT, GL.GL_RENDERBUFFER_EXT,
          depthStencilBuffer[0]);

      FBO.checkFBO();

      glstate.popFramebuffer();
    }
  }

  public void clear(int gray) {
    int c = parent.color(gray);
    glstate.clearColorBuffer(c);
  }

  public void clear(float gray) {
    int c = parent.color(gray);
    glstate.clearColorBuffer(c);
  }

  public void clear(int gray, int alpha) {
    int c = parent.color(gray, alpha);
    glstate.clearColorBuffer(c);
  }

  public void clear(int rgb, float alpha) {
    int c = parent.color(rgb, alpha);
    glstate.clearColorBuffer(c);
  }

  public void clear(float gray, float alpha) {
    int c = parent.color(gray, alpha);
    glstate.clearColorBuffer(c);
  }

  public void clear(int x, int y, int z) {
    int c = parent.color(x, y, z);
    glstate.clearColorBuffer(c);
  }

  public void clear(float x, float y, float z) {
    int c = parent.color(x, y, z);
    glstate.clearColorBuffer(c);
  }

  public void clear(int x, int y, int z, int a) {
    int c = parent.color(x, y, z, a);
    glstate.clearColorBuffer(c);
  }

  public void clear(float x, float y, float z, float a) {
    int c = parent.color(x, y, z, a);
    glstate.clearColorBuffer(c);
  }

  protected void allocate() {
    if (glstate == null) {
      PGraphicsOpenGL pgl = (PGraphicsOpenGL) parent.g;
      if (pgl instanceof GLGraphics) {
        glcaps = ((GLGraphics) pgl).getCapabilities();

        mainRenderer = (GLGraphics) pgl;
      } else {
        glcaps = null;

        mainRenderer = null;
      }
      context = pgl.getContext();
      drawable = null;

      initFramebuffer();
      settingsInited = false;
    } else
      reapplySettings();
  }

  public void setBlendMode(int MODE) {
    blend = true;
    blendMode = MODE;
  }

  protected void renderTriangles(int start, int stop) {
    report("render_triangles in");

    blend0 = gl.glIsEnabled(GL.GL_BLEND);
    if (blend) {
      gl.glEnable(GL.GL_BLEND);
      if (blendMode == BLEND)
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
      else if (blendMode == ADD)
        gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
      else if (blendMode == MULTIPLY)
        gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR);
      else if (blendMode == SUBTRACT)
        gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ZERO);
      else if (blendMode == BACKGROUND_ALPHA) {
        gl.glBlendColor(0.0f, 0.0f, 0.0f, 1.0f);
        gl.glBlendFunc(GL.GL_ONE, GL.GL_CONSTANT_COLOR);
      }
    }

    for (int i = start; i < stop; i++) {
      float a[] = vertices[triangles[i][VERTEX1]];
      float b[] = vertices[triangles[i][VERTEX2]];
      float c[] = vertices[triangles[i][VERTEX3]];

      // This is only true when not textured.
      // We really should pass specular straight through to triangle rendering.
      float ar = clamp(triangleColors[i][0][TRI_DIFFUSE_R]
          + triangleColors[i][0][TRI_SPECULAR_R]);
      float ag = clamp(triangleColors[i][0][TRI_DIFFUSE_G]
          + triangleColors[i][0][TRI_SPECULAR_G]);
      float ab = clamp(triangleColors[i][0][TRI_DIFFUSE_B]
          + triangleColors[i][0][TRI_SPECULAR_B]);
      float br = clamp(triangleColors[i][1][TRI_DIFFUSE_R]
          + triangleColors[i][1][TRI_SPECULAR_R]);
      float bg = clamp(triangleColors[i][1][TRI_DIFFUSE_G]
          + triangleColors[i][1][TRI_SPECULAR_G]);
      float bb = clamp(triangleColors[i][1][TRI_DIFFUSE_B]
          + triangleColors[i][1][TRI_SPECULAR_B]);
      float cr = clamp(triangleColors[i][2][TRI_DIFFUSE_R]
          + triangleColors[i][2][TRI_SPECULAR_R]);
      float cg = clamp(triangleColors[i][2][TRI_DIFFUSE_G]
          + triangleColors[i][2][TRI_SPECULAR_G]);
      float cb = clamp(triangleColors[i][2][TRI_DIFFUSE_B]
          + triangleColors[i][2][TRI_SPECULAR_B]);

      int textureIndex = triangles[i][TEXTURE_INDEX];
      if (textureIndex != -1) {
        report("before enable");
        gl.glEnable(GL.GL_TEXTURE_2D);
        report("after enable");

        float uscale = 1.0f;
        float vscale = 1.0f;

        PImage texture = textures[textureIndex];
        if (texture instanceof GLTexture) {
          GLTexture tex = (GLTexture) texture;
          gl.glBindTexture(tex.getTextureTarget(), tex.getTextureID());

          uscale *= tex.getMaxTextureCoordS();
          vscale *= tex.getMaxTextureCoordT();

          float cx = 0.0f;
          float sx = +1.0f;
          if (tex.isFlippedX()) {
            cx = 1.0f;
            sx = -1.0f;
          }

          float cy = 0.0f;
          float sy = +1.0f;
          if (tex.isFlippedY()) {
            cy = 1.0f;
            sy = -1.0f;
          }

          gl.glBegin(GL.GL_TRIANGLES);

          gl.glColor4f(ar, ag, ab, a[A]);
          gl.glTexCoord2f((cx + sx * a[U]) * uscale, (cy + sy * a[V]) * vscale);
          gl.glNormal3f(a[NX], a[NY], a[NZ]);
          gl.glEdgeFlag(a[EDGE] == 1);
          gl.glVertex3f(a[VX], a[VY], a[VZ]);

          gl.glColor4f(br, bg, bb, b[A]);
          gl.glTexCoord2f((cx + sx * b[U]) * uscale, (cy + sy * b[V]) * vscale);
          gl.glNormal3f(b[NX], b[NY], b[NZ]);
          gl.glEdgeFlag(a[EDGE] == 1);
          gl.glVertex3f(b[VX], b[VY], b[VZ]);

          gl.glColor4f(cr, cg, cb, c[A]);
          gl.glTexCoord2f((cx + sx * c[U]) * uscale, (cy + sy * c[V]) * vscale);
          gl.glNormal3f(c[NX], c[NY], c[NZ]);
          gl.glEdgeFlag(a[EDGE] == 1);
          gl.glVertex3f(c[VX], c[VY], c[VZ]);

          gl.glEnd();

          gl.glBindTexture(tex.getTextureTarget(), 0);
        } else {
          // Default texturing using a PImage.

          report("before bind");
          bindTexture(texture);
          report("after bind");

          ImageCache cash = (ImageCache) texture.getCache(this);
          uscale = (float) texture.width / (float) cash.twidth;
          vscale = (float) texture.height / (float) cash.theight;

          gl.glBegin(GL.GL_TRIANGLES);

          // System.out.println(a[U] + " " + a[V] + " " + uscale + " " +
          // vscale);
          // System.out.println(ar + " " + ag + " " + ab + " " + a[A]);
          // ar = ag = ab = 1;
          gl.glColor4f(ar, ag, ab, a[A]);
          gl.glTexCoord2f(a[U] * uscale, a[V] * vscale);
          gl.glNormal3f(a[NX], a[NY], a[NZ]);
          gl.glEdgeFlag(a[EDGE] == 1);
          gl.glVertex3f(a[VX], a[VY], a[VZ]);

          gl.glColor4f(br, bg, bb, b[A]);
          gl.glTexCoord2f(b[U] * uscale, b[V] * vscale);
          gl.glNormal3f(b[NX], b[NY], b[NZ]);
          gl.glEdgeFlag(a[EDGE] == 1);
          gl.glVertex3f(b[VX], b[VY], b[VZ]);

          gl.glColor4f(cr, cg, cb, c[A]);
          gl.glTexCoord2f(c[U] * uscale, c[V] * vscale);
          gl.glNormal3f(c[NX], c[NY], c[NZ]);
          gl.glEdgeFlag(a[EDGE] == 1);
          gl.glVertex3f(c[VX], c[VY], c[VZ]);

          gl.glEnd();

          report("non-binding 6");

          gl.glDisable(GL.GL_TEXTURE_2D);
        }
      } else {
        // no texture
        gl.glBegin(GL.GL_TRIANGLES);

        gl.glColor4f(ar, ag, ab, a[A]);
        gl.glNormal3f(a[NX], a[NY], a[NZ]);
        gl.glEdgeFlag(a[EDGE] == 1);
        gl.glVertex3f(a[VX], a[VY], a[VZ]);

        gl.glColor4f(br, bg, bb, b[A]);
        gl.glNormal3f(b[NX], b[NY], b[NZ]);
        gl.glEdgeFlag(a[EDGE] == 1);
        gl.glVertex3f(b[VX], b[VY], b[VZ]);

        gl.glColor4f(cr, cg, cb, c[A]);
        gl.glNormal3f(c[NX], c[NY], c[NZ]);
        gl.glEdgeFlag(a[EDGE] == 1);
        gl.glVertex3f(c[VX], c[VY], c[VZ]);

        gl.glEnd();
      }
    }

    // If there was noblending originally
    if (!blend0 && blend)
      gl.glDisable(GL.GL_BLEND);
    // Default blending mode in PGraphicsOpenGL.
    gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);

    report("render_triangles out");
  }

  protected void loadGLModelviewMatrix() {
    gl.glMatrixMode(GL.GL_MODELVIEW);
    copyModelviewTM();
    gl.glLoadMatrixf(modelviewTM, 0);
  }

  protected void multGLModelviewMatrix() {
    gl.glMatrixMode(GL.GL_MODELVIEW);
    copyModelviewTM();
    gl.glMultMatrixf(modelviewTM, 0);
  }

  protected void copyModelviewTM() {
    if (modelviewTM == null)
      modelviewTM = new float[16];

    modelviewTM[0] = modelview.m00;
    modelviewTM[1] = modelview.m10;
    modelviewTM[2] = modelview.m20;
    modelviewTM[3] = modelview.m30;

    modelviewTM[4] = modelview.m01;
    modelviewTM[5] = modelview.m11;
    modelviewTM[6] = modelview.m21;
    modelviewTM[7] = modelview.m31;

    modelviewTM[8] = modelview.m02;
    modelviewTM[9] = modelview.m12;
    modelviewTM[10] = modelview.m22;
    modelviewTM[11] = modelview.m32;

    modelviewTM[12] = modelview.m03;
    modelviewTM[13] = modelview.m13;
    modelviewTM[14] = modelview.m23;
    modelviewTM[15] = modelview.m33;
  }
}
