/**
 * This package provides classes to facilitate the handling of opengl textures, glsl shaders and 
 * off-screen rendering in Processing.
 * @author Andres Colubri. Suggestions and contributions from Aaron Koblin and sigg mus
 * @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 processing.xml.XMLElement;

import javax.media.opengl.*;

import com.sun.opengl.util.BufferUtil;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.FloatBuffer;
import java.util.ArrayList;

/**
 * This class holds a 3D model composed of vertices, normals, colors (per
 * vertex) and texture coordinates (also per vertex). All this data is stored in
 * Vertex Buffer Objects (VBO) for fast access. This is class is still
 * undergoing development, the API will probably change quickly in the following
 * months as features are tested and refined. In particular, with the settings
 * of the VBOs in this first implementation (GL.GL_DYNAMIC_DRAW_ARB) it is
 * assumed that the coordinates will change often during the lifetime of the
 * model. For static models a different VBO setting (GL.GL_STATIC_DRAW_ARB)
 * should be used.
 */
public class GLModel implements PConstants, GLConstants {
  protected PApplet parent;
  protected GL gl;
  protected PGraphicsOpenGL pgl;
  protected int size;
  protected int vertexMode;
  protected int vboUsage;
  protected int[] vertCoordsVBO = { 0 };
  protected String description;

  protected float[] tmpVertArray;
  protected float[] tmpColorArray;
  protected float[] tmpNormalsArray;
  protected float[] tmpTexCoordsArray;
  protected float[] tmpAttributesArray;

  protected int[] colorsVBO = null;
  protected int[] normCoordsVBO = null;
  protected int[] texCoordsVBO = null;

  protected float tintR, tintG, tintB, tintA;
  protected float[] specularColor = { 1.0f, 1.0f, 1.0f, 1.0f };
  protected float[] emissiveColor = { 0.0f, 0.0f, 0.0f, 1.0f };
  protected float[] shininess = { 0 };

  protected float pointSize;
  protected float maxPointSize;
  protected float lineWidth;
  protected boolean usingPointSprites;
  protected boolean blend;
  protected boolean blend0;
  protected int blendMode;
  protected float spriteFadeSize;

  protected int numAttributes;
  protected int[] attribVBO = null;
  protected String[] attribName;
  protected int[] attribSize;
  protected int curtAttrSize;

  protected int numTextures;

  public GLTexture[] textures;

  public FloatBuffer vertices;
  public FloatBuffer colors;
  public FloatBuffer normals;
  public FloatBuffer texCoords;
  public FloatBuffer attributes;

  public static final int STATIC = 0;
  public static final int DYNAMIC = 1;
  public static final int STREAM = 2;

  protected float ptDistAtt[] = { 1.0f, 0.0f, 0.01f, 1.0f };  
  
  /**
   * Creates an instance of GLModel with the specified parameters: number of
   * vertices, mode to draw the vertices (as points, sprites, lines, etc) and
   * usage (static if the vertices will never change after the first time are
   * initialized, dynamic if they will change frequently or stream if they will
   * change at every frame).
   * 
   * @param parent
   *          PApplet
   * @param numVert
   *          int
   * @param mode
   *          int
   * @param usage
   *          int
   */
  public GLModel(PApplet parent, int numVert, int mode, int usage) {
    initModelCommon(parent);
    size = numVert;

    if (mode == POINTS)
      vertexMode = GL.GL_POINTS;
    else if (mode == POINT_SPRITES) {
      vertexMode = GL.GL_POINTS;
      usingPointSprites = true;
      float[] tmp = { 0.0f };
      gl.glGetFloatv(GL.GL_POINT_SIZE_MAX_ARB, tmp, 0);
      maxPointSize = tmp[0];
      pointSize = maxPointSize;
      spriteFadeSize = 0.6f * pointSize;
    } else if (mode == LINES)
      vertexMode = GL.GL_LINES;
    else if (mode == LINE_STRIP)
      vertexMode = GL.GL_LINE_STRIP;
    else if (mode == LINE_LOOP)
      vertexMode = GL.GL_LINE_LOOP;
    else if (mode == TRIANGLES)
      vertexMode = GL.GL_TRIANGLES;
    else if (mode == TRIANGLE_FAN)
      vertexMode = GL.GL_TRIANGLE_FAN;
    else if (mode == TRIANGLE_STRIP)
      vertexMode = GL.GL_TRIANGLE_STRIP;
    else if (mode == QUADS)
      vertexMode = GL.GL_QUADS;
    else if (mode == QUAD_STRIP)
      vertexMode = GL.GL_QUAD_STRIP;
    else if (mode == POLYGON)
      vertexMode = GL.GL_POLYGON;

    if (usage == STATIC)
      vboUsage = GL.GL_STATIC_DRAW_ARB;
    else if (usage == DYNAMIC)
      vboUsage = GL.GL_DYNAMIC_DRAW_ARB;
    else if (usage == STREAM)
      vboUsage = GL.GL_STREAM_COPY;

    gl.glGenBuffersARB(1, vertCoordsVBO, 0);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, vertCoordsVBO[0]);
    gl.glBufferDataARB(GL.GL_ARRAY_BUFFER_ARB, size * 4
        * BufferUtil.SIZEOF_FLOAT, null, vboUsage);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);

    description = "Just another GLModel";
  }

  public GLModel(PApplet parent, float[] vertArray, int mode, int usage) {
    this(parent, vertArray.length / 4, mode, usage);
    updateVertices(vertArray);
  }

  public GLModel(PApplet parent, ArrayList<PVector> vertArrayList, int mode,
      int usage) {
    this(parent, vertArrayList.size(), mode, usage);
    updateVertices(vertArrayList);
  }

  public GLModel(PApplet parent, String filename) {
    initModelCommon(parent);
    this.parent = parent;

    filename = filename.replace('\\', '/');
    XMLElement xml = new XMLElement(parent, filename);

    loadXML(xml);
  }

  public GLModel(PApplet parent, URL url) {
    initModelCommon(parent);
    this.parent = parent;

    try {
      String xmlText = PApplet
          .join(PApplet.loadStrings(url.openStream()), "\n");
      XMLElement xml = new XMLElement(xmlText);
      loadXML(xml);
    } catch (IOException e) {
      System.err.println("Error loading effect: " + e.getMessage());
    }
  }

  protected void finalize() throws Throwable {
    if (vertCoordsVBO[0] != 0) {
      gl.glDeleteBuffers(1, vertCoordsVBO, 0);
    }

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

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

    if (texCoordsVBO != null && texCoordsVBO[0] != 0) {
      gl.glDeleteBuffers(numTextures, texCoordsVBO, 0);
    }

    if (attribVBO != null && attribVBO[0] != 0) {
      gl.glDeleteBuffers(numAttributes, attribVBO, 0);
    }

    super.finalize();
  }

  public int getSize() {
    return size;
  }

  public String getDescription() {
    return description;
  }

  /**
   * Returns the OpenGL identifier of the Vertex Buffer Object holding the
   * coordinates of this model.
   * 
   * @return int
   */
  public int getCoordsVBO() {
    return vertCoordsVBO[0];
  }

  /**
   * This method creates the normals, i.e.: it creates the internal OpenGL
   * variables to store normal data.
   */
  public void initNormals() {
    normCoordsVBO = new int[1];
    gl.glGenBuffersARB(1, normCoordsVBO, 0);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, normCoordsVBO[0]);
    gl.glBufferDataARB(GL.GL_ARRAY_BUFFER_ARB, size * 4
        * BufferUtil.SIZEOF_FLOAT, null, vboUsage);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * This method creates the colors, i.e.: it creates the internal OpenGL
   * variables to store color data.
   */
  public void initColors() {
    colorsVBO = new int[1];
    gl.glGenBuffersARB(1, colorsVBO, 0);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, colorsVBO[0]);
    gl.glBufferDataARB(GL.GL_ARRAY_BUFFER_ARB, size * 4
        * BufferUtil.SIZEOF_FLOAT, null, vboUsage);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * Sets the number of attributes for this model. An attribute is a vector
   * value associated to each vertex of the model, and can be used for any
   * custom calculation (lighting, deformations, etc). This custom calculations
   * can be performed using a GLModelEffect.
   * 
   * @param n
   *          int
   */
  public void initAttributes(int n) {
    numAttributes = n;
    attribVBO = new int[n];
    attribName = new String[n];
    attribSize = new int[n];
    gl.glGenBuffersARB(n, attribVBO, 0);
  }

  /**
   * Sets the name and size (number of components per vertex) of attribute i.
   * 
   * @param i
   *          int
   * @param aname
   *          String
   * @param asize
   *          int
   */
  public void setAttribute(int i, String aname, int asize) {
    attribName[i] = aname;
    attribSize[i] = asize;

    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, attribVBO[i]);
    gl.glBufferDataARB(GL.GL_ARRAY_BUFFER_ARB, size * asize
        * BufferUtil.SIZEOF_FLOAT, null, vboUsage);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * This method creates n textures, i.e.: it creates the internal OpenGL
   * variables to store n textures.
   * 
   * @param n
   *          int
   */
  public void initTextures(int n) {
    numTextures = n;

    texCoordsVBO = new int[numTextures];
    textures = new GLTexture[numTextures];
    gl.glGenBuffersARB(numTextures, texCoordsVBO, 0);
    for (n = 0; n < numTextures; n++) {
      gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, texCoordsVBO[n]); // Bind the
                                                                   // buffer.
      gl.glBufferDataARB(GL.GL_ARRAY_BUFFER_ARB, size * 2
          * BufferUtil.SIZEOF_FLOAT, null, vboUsage);
    }
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * Sets the i-th texture.
   * 
   * @param i
   *          int
   */
  public void setTexture(int i, GLTexture tex) {
    textures[i] = tex;
  }

  /**
   * Returns the number of textures.
   * 
   * @return int
   */
  public int getNumTextures() {
    return numTextures;
  }

  /**
   * Returns the i-th texture.
   * 
   * @return GLTexture
   */
  public GLTexture getTexture(int i) {
    return textures[i];
  }

  /**
   * Enables vertex updating, to be done with the
   * updateVertex()/displaceVertex() methods.
   */
  public void beginUpdateVertices() {
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, vertCoordsVBO[0]);
    vertices = gl.glMapBufferARB(GL.GL_ARRAY_BUFFER_ARB, GL.GL_WRITE_ONLY)
        .asFloatBuffer();
  }

  /**
   * Disables vertex updating.
   */
  public void endUpdateVertices() {
    if (tmpVertArray != null) {
      vertices.put(tmpVertArray);
      tmpVertArray = null;
    }
    vertices.position(0);
    gl.glUnmapBufferARB(GL.GL_ARRAY_BUFFER_ARB);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * Returns array list with vertices stored in binary file.
   * 
   * @param String
   *          filename
   * @return ArrayList<PVector>
   */
  public ArrayList<PVector> getVertices(String filename) {
    ArrayList<PVector> res = new ArrayList<PVector>();
    loadPVectorArrayListFromBinary(filename, res, 3);
    return res;
  }

  /**
   * Saves vertices in the given array list to binary file.
   * 
   * @param String
   *          filename
   * @param ArrayList
   *          <PVector> verts
   */
  public void saveVertices(String filename, ArrayList<PVector> verts) {
    savePVectorArrayListToBinary(filename, verts, 3);
  }

  /**
   * Loads vertices from binary file into model.
   * 
   * @param String
   *          filename
   */
  public void loadVertices(String filename) {
    float[] tmp = new float[4 * size];
    for (int i = 0; i < size; i++)
      tmp[4 * i + 3] = 1.0f;

    beginUpdateVertices();
    loadFloatArrayFromBinary(filename, tmp, size, 4, 3);
    vertices.put(tmp);
    endUpdateVertices();
  }

  /**
   * Saves vertices in the model to binary file.
   */
  public void saveVertices(String filename) {
    float[] tmp = new float[4 * size];

    beginUpdateVertices();
    vertices.get(tmp);
    saveFloatArrayToBinary(filename, tmp, size, 4, 3);
    endUpdateVertices();
  }

  /**
   * Updates the coordinates of vertex idx.
   * 
   * @param idx
   *          int
   * @param x
   *          float
   * @param y
   *          float
   */
  public void updateVertex(int idx, float x, float y) {
    updateVertex(idx, x, y, 0, 1);
  }

  /**
   * Updates the coordinates of vertex idx.
   * 
   * @param idx
   *          int
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   */
  public void updateVertex(int idx, float x, float y, float z) {
    updateVertex(idx, x, y, z, 1);
  }

  /**
   * Updates the coordinates of vertex idx.
   * 
   * @param idx
   *          int
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   * @param w
   *          float
   */
  public void updateVertex(int idx, float x, float y, float z, float w) {
    if (tmpVertArray == null) {
      tmpVertArray = new float[4 * size];
      vertices.get(tmpVertArray);
      vertices.rewind();
    }

    tmpVertArray[4 * idx + 0] = x;
    tmpVertArray[4 * idx + 1] = y;
    tmpVertArray[4 * idx + 2] = z;
    tmpVertArray[4 * idx + 3] = w;
  }

  /**
   * Adds a displacement (dx, dy) to the vertex idx.
   * 
   * @param idx
   *          int
   * @param dx
   *          float
   * @param dy
   *          float
   */
  public void displaceVertex(int idx, float dx, float dy) {
    displaceVertex(idx, dx, dy, 0, 0);
  }

  /**
   * Adds a displacement (dx, dy, dz) to the vertex idx.
   * 
   * @param idx
   *          int
   * @param dx
   *          float
   * @param dy
   *          float
   * @param dz
   *          float
   */
  public void displaceVertex(int idx, float dx, float dy, float dz) {
    displaceVertex(idx, dx, dy, dz, 0);
  }

  /**
   * Adds a displacement (dx, dy, dz, dw) to the vertex idx.
   * 
   * @param idx
   *          int
   * @param dx
   *          float
   * @param dy
   *          float
   * @param dz
   *          float
   * @param dw
   *          float
   */
  public void displaceVertex(int idx, float dx, float dy, float dz, float dw) {
    if (tmpVertArray == null) {
      tmpVertArray = new float[4 * size];
      vertices.get(tmpVertArray);
      vertices.rewind();
    }

    tmpVertArray[4 * idx + 0] += dx;
    tmpVertArray[4 * idx + 1] += dy;
    tmpVertArray[4 * idx + 2] += dz;
    tmpVertArray[4 * idx + 3] += dw;
  }

  /**
   * Updates all the vertices using the coordinates provided in the array
   * vertArray.
   * 
   * @param vertArray
   *          float[]
   */
  public void updateVertices(float[] vertArray) {
    beginUpdateVertices();
    vertices.put(vertArray);
    endUpdateVertices();
  }

  /**
   * Updates all the vertices using the coordinates provided in the array
   * vertArray.
   * 
   * @param vertArrayList
   *          ArrayList<PVector>
   */
  public void updateVertices(ArrayList<PVector> vertArrayList) {
    if (vertArrayList.size() != size) {
      System.err.println("Wrong number of vertices in the array list.");
      return;
    }

    float p[] = new float[4 * size];
    for (int i = 0; i < vertArrayList.size(); i++) {
      PVector point = (PVector) vertArrayList.get(i);
      p[4 * i + 0] = point.x;
      p[4 * i + 1] = point.y;
      p[4 * i + 2] = point.z;
      p[4 * i + 3] = 1.0f;
    }
    updateVertices(p);
  }

  /**
   * Centers the model to (0, 0, 0).
   */
  public void centerVertices() {
    centerVertices(0, 0, 0);
  }

  /**
   * Centers the model to (xc, yc, 0).
   * 
   * @param xc
   *          float
   * @param yc
   *          float
   */
  public void centerVertices(float xc, float yc) {
    centerVertices(xc, yc, 0);
  }

  /**
   * Centers the model to (xc, yc, zc).
   * 
   * @param xc
   *          float
   * @param yc
   *          float
   * @param zc
   *          float
   */
  public void centerVertices(float xc, float yc, float zc) {
    beginUpdateVertices();
    tmpVertArray = new float[4 * size];
    vertices.get(tmpVertArray);
    vertices.rewind();

    float xave, yave, zave;
    xave = yave = zave = 0;
    for (int i = 0; i < size; i++) {
      xave += tmpVertArray[4 * i + 0];
      yave += tmpVertArray[4 * i + 1];
      zave += tmpVertArray[4 * i + 2];
    }
    xave /= size;
    yave /= size;
    zave /= size;

    for (int i = 0; i < size; i++) {
      tmpVertArray[4 * i + 0] += xc - xave;
      tmpVertArray[4 * i + 1] += yc - yave;
      tmpVertArray[4 * i + 2] += zc - zave;
    }

    endUpdateVertices();
  }

  public void beginUpdateColors() {
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, colorsVBO[0]);
    colors = gl.glMapBufferARB(GL.GL_ARRAY_BUFFER_ARB, GL.GL_WRITE_ONLY)
        .asFloatBuffer();
  }

  public void endUpdateColors() {
    if (tmpColorArray != null) {
      colors.put(tmpColorArray);
      tmpColorArray = null;
    }
    colors.position(0);
    gl.glUnmapBufferARB(GL.GL_ARRAY_BUFFER_ARB);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * Returns array list with colors stored in binary file.
   * 
   * @param String
   *          filename
   * @return ArrayList<PVector>
   */
  public ArrayList<float[]> getColors(String filename) {
    ArrayList<float[]> res = new ArrayList<float[]>();
    loadFloatArrayListFromBinary(filename, res, 4);
    return res;
  }

  /**
   * Saves colors in the given array list to binary file.
   * 
   * @param String
   *          filename
   * @param ArrayList
   *          <PVector> icolors
   */
  public void saveColors(String filename, ArrayList<float[]> icolors) {
    saveFloatArrayToBinary(filename, icolors, 4);
  }

  /**
   * Loads colors from binary file into model.
   * 
   * @param String
   *          filename
   */
  public void loadColors(String filename) {
    float[] tmp = new float[4 * size];

    beginUpdateColors();
    loadFloatArrayFromBinary(filename, tmp, size, 4, 4);
    colors.put(tmp);
    endUpdateColors();
  }

  /**
   * Saves colors in the model to binary file.
   */
  public void saveColors(String filename) {
    float[] tmp = new float[4 * size];

    beginUpdateColors();
    colors.get(tmp);
    saveFloatArrayToBinary(filename, tmp, size, 4, 4);
    endUpdateColors();
  }

  /**
   * Paints the vertex i with the specified gray tone.
   * 
   * @param gray
   *          float
   */
  public void updateColor(int i, float gray) {
    int c = parent.color(gray);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints the vertex i with the specified gray tone and alpha value.
   * 
   * @param gray
   *          int
   * @param alpha
   *          int
   */
  public void updateColor(int i, int gray, int alpha) {
    int c = parent.color(gray, alpha);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints the vertex i with the specified rgb color and alpha value.
   * 
   * @param rgb
   *          int
   * @param alpha
   *          float
   */
  public void updateColor(int i, int rgb, float alpha) {
    int c = parent.color(rgb, alpha);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints the vertex i with the specified gray tone and alpha value.
   * 
   * @param gray
   *          float
   * @param alpha
   *          float
   */
  public void updateColor(int i, float gray, float alpha) {
    int c = parent.color(gray, alpha);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints the vertex i with the specified color components.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   */
  public void updateColor(int i, int x, int y, int z) {
    int c = parent.color(x, y, z);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints the vertex i with the specified color components.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   */
  public void updateColor(int i, float x, float y, float z) {
    int c = parent.color(x, y, z);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints the vertex i with the specified color components and alpha
   * component.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   * @param a
   *          int
   */
  public void updateColor(int i, int x, int y, int z, int a) {
    int c = parent.color(x, y, z, a);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints the vertex i with the specified color components and alpha
   * component.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   * @param a
   *          float
   */
  public void updateColor(int i, float x, float y, float z, float a) {
    int c = parent.color(x, y, z, a);
    putColorInTmpArray(i, c);
  }

  /**
   * Paints all vertices with the specified gray tone.
   * 
   * @param gray
   *          float
   */
  public void setColors(float gray) {
    int c = parent.color(gray);
    updateAllColors(c);
  }

  /**
   * Paints all vertices with the specified gray tone and alpha value.
   * 
   * @param gray
   *          int
   * @param alpha
   *          int
   */
  public void setColors(int gray, int alpha) {
    int c = parent.color(gray, alpha);
    updateAllColors(c);
  }

  /**
   * Paints all vertices with the specified rgb color and alpha value.
   * 
   * @param rgb
   *          int
   * @param alpha
   *          float
   */
  public void setColors(int rgb, float alpha) {
    int c = parent.color(rgb, alpha);
    updateAllColors(c);
  }

  /**
   * Paints all vertices with the specified gray tone and alpha value.
   * 
   * @param gray
   *          float
   * @param alpha
   *          float
   */
  public void setColors(float gray, float alpha) {
    int c = parent.color(gray, alpha);
    updateAllColors(c);
  }

  /**
   * Paints all vertices with the specified color components.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   */
  public void setColors(int x, int y, int z) {
    int c = parent.color(x, y, z);
    updateAllColors(c);
  }

  /**
   * Paints all vertices with the specified color components.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   */
  public void setColors(float x, float y, float z) {
    int c = parent.color(x, y, z);
    updateAllColors(c);
  }

  /**
   * Paints all vertices with the specified color components and alpha
   * component.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   * @param a
   *          int
   */
  public void setColors(int x, int y, int z, int a) {
    int c = parent.color(x, y, z, a);
    updateAllColors(c);
  }

  /**
   * Paints all vertices with the specified color components and alpha
   * component.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   * @param a
   *          float
   */
  public void setColors(float x, float y, float z, float a) {
    int c = parent.color(x, y, z, a);
    updateAllColors(c);
  }

  public void updateColors(float[] colArray) {
    beginUpdateColors();
    colors.put(colArray);
    endUpdateColors();
  }

  public void updateColors(ArrayList<float[]> colArrayList) {
    if (colArrayList.size() != size) {
      System.err.println("Wrong number of colors in the array list.");
      return;
    }

    float p[] = new float[4 * size];
    for (int i = 0; i < colArrayList.size(); i++) {
      float[] c = (float[]) colArrayList.get(i);

      if (c.length == 4) {
        p[4 * i + 0] = c[0];
        p[4 * i + 1] = c[1];
        p[4 * i + 2] = c[2];
        p[4 * i + 3] = c[3];
      }
    }
    updateColors(p);
  }

  public void beginUpdateTexCoords(int n) {
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, texCoordsVBO[n]);
    texCoords = gl.glMapBufferARB(GL.GL_ARRAY_BUFFER_ARB, GL.GL_WRITE_ONLY)
        .asFloatBuffer();
  }

  public void updateTexCoord(int idx, float s, float t) {
    if (tmpTexCoordsArray == null) {
      tmpTexCoordsArray = new float[2 * size];
      texCoords.get(tmpTexCoordsArray);
      texCoords.rewind();
    }

    tmpTexCoordsArray[2 * idx + 0] = s;
    tmpTexCoordsArray[2 * idx + 1] = t;
  }

  /**
   * Returns array list with texture coordinates stored in binary file.
   * 
   * @param String
   *          filename
   * @return ArrayList<PVector>
   */
  public ArrayList<PVector> getTexCoords(String filename) {
    ArrayList<PVector> res = new ArrayList<PVector>();
    loadPVectorArrayListFromBinary(filename, res, 2);
    return res;
  }

  /**
   * Saves texture coordinates in the given array list to binary file.
   * 
   * @param String
   *          filename
   * @param ArrayList
   *          <PVector> icolors
   */
  public void saveTexCoords(String filename, ArrayList<PVector> itexCoords) {
    savePVectorArrayListToBinary(filename, itexCoords, 2);
  }

  /**
   * Loads texture coordinates of nth texture from binary file into model.
   * 
   * @param n
   * @param String
   *          filename
   */
  public void loadTexCoords(int n, String filename) {
    float[] tmp = new float[2 * size];

    beginUpdateTexCoords(n);
    loadFloatArrayFromBinary(filename, tmp, size, 2, 2);
    texCoords.put(tmp);
    endUpdateTexCoords();
  }

  /**
   * Saves colors in the model to binary file.
   */
  public void saveTexCoords(int n, String filename) {
    float[] tmp = new float[2 * size];

    beginUpdateTexCoords(n);
    texCoords.get(tmp);
    saveFloatArrayToBinary(filename, tmp, size, 2, 2);
    endUpdateTexCoords();
  }

  public void displaceTexCoord(int idx, float ds, float dt) {
    if (tmpTexCoordsArray == null) {
      tmpTexCoordsArray = new float[2 * size];
      texCoords.get(tmpTexCoordsArray);
      texCoords.rewind();
    }

    tmpTexCoordsArray[2 * idx + 0] += ds;
    tmpTexCoordsArray[2 * idx + 1] += dt;
  }

  public void endUpdateTexCoords() {
    if (tmpTexCoordsArray != null) {
      texCoords.put(tmpTexCoordsArray);
      tmpTexCoordsArray = null;
    }
    texCoords.position(0);
    gl.glUnmapBufferARB(GL.GL_ARRAY_BUFFER_ARB);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  public void updateTexCoords(int n, float[] texCoordsArray) {
    beginUpdateTexCoords(n);
    texCoords.put(texCoordsArray);
    endUpdateTexCoords();
  }

  public void updateTexCoords(int n, ArrayList<PVector> texCoordsArrayList) {
    if (texCoordsArrayList.size() != size) {
      System.err
          .println("Wrong number of texture coordinates in the array list.");
      return;
    }

    float p[] = new float[2 * size];
    for (int i = 0; i < texCoordsArrayList.size(); i++) {
      PVector point = (PVector) texCoordsArrayList.get(i);
      p[2 * i + 0] = point.x;
      p[2 * i + 1] = point.y;
    }
    updateTexCoords(n, p);
  }

  public void beginUpdateNormals() {
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, normCoordsVBO[0]);
    normals = gl.glMapBufferARB(GL.GL_ARRAY_BUFFER_ARB, GL.GL_WRITE_ONLY)
        .asFloatBuffer();
  }

  public void endUpdateNormals() {
    if (tmpNormalsArray != null) {
      normals.put(tmpNormalsArray);
      tmpNormalsArray = null;
    }
    normals.position(0);

    gl.glUnmapBufferARB(GL.GL_ARRAY_BUFFER_ARB);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * Returns array list with normals stored in binary file.
   * 
   * @param String
   *          filename
   * @return ArrayList<PVector>
   */
  public ArrayList<PVector> getNormals(String filename) {
    ArrayList<PVector> res = new ArrayList<PVector>();
    loadPVectorArrayListFromBinary(filename, res, 3);
    return res;
  }

  /**
   * Saves normals in the given array list to binary file.
   * 
   * @param String
   *          filename
   * @param ArrayList
   *          <PVector> verts
   */
  public void saveNormals(String filename, ArrayList<PVector> norms) {
    savePVectorArrayListToBinary(filename, norms, 3);
  }

  /**
   * Loads normals from binary file into model.
   * 
   * @param String
   *          filename
   */
  public void loadNormals(String filename) {
    float[] tmp = new float[4 * size];

    beginUpdateNormals();
    loadFloatArrayFromBinary(filename, tmp, size, 4, 3);
    normals.put(tmp);
    endUpdateNormals();
  }

  /**
   * Saves normals in the model to binary file.
   */
  public void saveNormals(String filename) {
    float[] tmp = new float[4 * size];

    beginUpdateNormals();
    normals.get(tmp);
    saveFloatArrayToBinary(filename, tmp, size, 4, 3);
    endUpdateNormals();
  }

  public void updateNormal(int idx, float x, float y) {
    updateNormal(idx, x, y, 0, 0);
  }

  public void updateNormal(int idx, float x, float y, float z) {
    updateNormal(idx, x, y, z, 0);
  }

  public void updateNormal(int idx, float x, float y, float z, float w) {
    if (tmpNormalsArray == null) {
      tmpNormalsArray = new float[4 * size];
      normals.get(tmpNormalsArray);
      normals.rewind();
    }

    tmpNormalsArray[4 * idx + 0] = x;
    tmpNormalsArray[4 * idx + 1] = y;
    tmpNormalsArray[4 * idx + 2] = z;
    tmpNormalsArray[4 * idx + 3] = w;
  }

  public void displaceNormal(int idx, float dx, float dy) {
    displaceNormal(idx, dx, dy, 0, 0);
  }

  public void displaceNormal(int idx, float dx, float dy, float dz) {
    displaceNormal(idx, dx, dy, dz, 0);
  }

  public void displaceNormal(int idx, float dx, float dy, float dz, float dw) {
    if (tmpNormalsArray == null) {
      tmpNormalsArray = new float[4 * size];
      normals.get(tmpNormalsArray);
      normals.rewind();
    }

    tmpNormalsArray[4 * idx + 0] += dx;
    tmpNormalsArray[4 * idx + 1] += dy;
    tmpNormalsArray[4 * idx + 2] += dz;
    tmpNormalsArray[4 * idx + 3] += dw;
  }

  public void updateNormals(float[] normArray) {
    beginUpdateNormals();
    normals.put(normArray);
    endUpdateNormals();
  }

  public void updateNormals(ArrayList<PVector> normArrayList) {
    if (normArrayList.size() != size) {
      System.err.println("Wrong number of normals in the array list.");
      return;
    }

    float p[] = new float[4 * size];
    for (int i = 0; i < normArrayList.size(); i++) {
      PVector point = (PVector) normArrayList.get(i);
      p[4 * i + 0] = point.x;
      p[4 * i + 1] = point.y;
      p[4 * i + 2] = point.z;
      p[4 * i + 3] = 0.0f;
    }
    updateNormals(p);
  }

  public void beginUpdateAttributes(int n) {
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, attribVBO[n]);
    attributes = gl.glMapBufferARB(GL.GL_ARRAY_BUFFER_ARB, GL.GL_WRITE_ONLY)
        .asFloatBuffer();
    curtAttrSize = attribSize[n];
  }

  public void endUpdateAttributes() {
    if (tmpAttributesArray != null) {
      attributes.put(tmpAttributesArray);
      tmpAttributesArray = null;
    }
    attributes.position(0);
    gl.glUnmapBufferARB(GL.GL_ARRAY_BUFFER_ARB);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
  }

  /**
   * Returns array list with attributes stored in binary file. Each attribute
   * has size len.
   * 
   * @param String
   *          filename
   * @return ArrayList<PVector> int len
   */
  public ArrayList<float[]> getAttributes(String filename, int len) {
    ArrayList<float[]> res = new ArrayList<float[]>();
    loadFloatArrayListFromBinary(filename, res, len);
    return res;
  }

  /**
   * Saves attributes in the given array list to binary file. Each attribute has
   * size len.
   * 
   * @param String
   *          filename
   * @param ArrayList
   *          <PVector> iattrib
   */
  public void saveAttributes(String filename, ArrayList<float[]> iattrib,
      int len) {
    saveFloatArrayToBinary(filename, iattrib, len);
  }

  /**
   * Loads attribute nth from binary file into model.
   * 
   * @param String
   *          filename
   */
  public void loadAttributes(int n, String filename) {
    int len = attribSize[n];
    float[] tmp = new float[len * size];

    beginUpdateAttributes(n);
    loadFloatArrayFromBinary(filename, tmp, size, len, len);
    attributes.put(tmp);
    endUpdateAttributes();
  }

  /**
   * Saves attribute n-th in the model to binary file.
   */
  public void saveAttributes(int n, String filename) {
    int len = attribSize[n];
    float[] tmp = new float[len * size];

    beginUpdateAttributes(n);
    attributes.get(tmp);
    saveFloatArrayToBinary(filename, tmp, size, len, len);
    endUpdateAttributes();
  }

  public void updateAttribute(int idx, float x) {
    updateAttribute(idx, new float[] { x });
  }

  public void updateAttribute(int idx, float x, float y) {
    updateAttribute(idx, new float[] { x, y });
  }

  public void updateAttribute(int idx, float x, float y, float z) {
    updateAttribute(idx, new float[] { x, y, z });
  }

  public void updateAttribute(int idx, float x, float y, float z, float w) {
    updateAttribute(idx, new float[] { x, y, z, w });
  }

  public void updateAttribute(int idx, float[] values) {
    if (values.length == curtAttrSize) {
      if (tmpAttributesArray == null) {
        tmpAttributesArray = new float[curtAttrSize * size];
        attributes.get(tmpAttributesArray);
        attributes.rewind();
      }

      for (int i = 0; i < curtAttrSize; i++)
        tmpAttributesArray[curtAttrSize * idx + i] = values[i];
    }
  }

  public void displaceAttribute(int idx, float dx) {
    displaceAttribute(idx, new float[] { dx });
  }

  public void displaceAttribute(int idx, float dx, float dy) {
    displaceAttribute(idx, new float[] { dx, dy });
  }

  public void displaceAttribute(int idx, float x, float y, float z) {
    displaceAttribute(idx, new float[] { x, y, z });
  }

  public void displaceAttribute(int idx, float x, float y, float z, float w) {
    displaceAttribute(idx, new float[] { x, y, z, w });
  }

  public void displaceAttribute(int idx, float[] dvalues) {
    int l = attribSize[idx];
    if (dvalues.length == l) {
      if (tmpAttributesArray == null) {
        tmpAttributesArray = new float[l * size];
        attributes.get(tmpAttributesArray);
        attributes.rewind();
      }

      for (int i = 0; i < l; i++)
        tmpAttributesArray[l * idx + i] += dvalues[i];
    }
  }

  public void updateAttributes(int n, float[] attributesArray) {
    beginUpdateAttributes(n);
    attributes.put(attributesArray);
    endUpdateAttributes();
  }

  public void updateAttributes(int n, ArrayList<float[]> vertAttribsArrayList) {
    if (vertAttribsArrayList.size() != size) {
      System.err
          .println("Wrong number of vertex attributes in the array list.");
      return;
    }

    int l = attribSize[n];
    float p[] = new float[l * size];
    for (int i = 0; i < vertAttribsArrayList.size(); i++) {
      float[] attrib = (float[]) vertAttribsArrayList.get(i);

      for (int j = 0; j < l; j++)
        p[l * i + j] = attrib[j];
    }
    updateAttributes(n, p);
  }

  public void setLineWidth(float w) {
    lineWidth = w;
  }

  public void setPointSize(float s) {
    pointSize = s;
  }

  public float getMaxPointSize() {
    return maxPointSize;
  }

  public void setSpriteFadeSize(float s) {
    spriteFadeSize = s;
  }

  /**
   * Disables blending.
   */
  public void noBlend() {
    blend = false;
  }

  /**
   * Enables blending and sets the mode.
   * 
   * @param MODE
   *          int
   */
  public void setBlendMode(int MODE) {
    blend = true;
    blendMode = MODE;
  }

  /**
   * Set the tint color to the specified gray tone.
   * 
   * @param gray
   *          float
   */
  public void setTint(float gray) {
    int c = parent.color(gray);
    setTintColor(c);
  }

  /**
   * Set the tint color to the specified gray tone and alpha value.
   * 
   * @param gray
   *          int
   * @param alpha
   *          int
   */
  public void setTint(int gray, int alpha) {
    int c = parent.color(gray, alpha);
    setTintColor(c);
  }

  /**
   * Set the tint color to the specified rgb color and alpha value.
   * 
   * @param rgb
   *          int
   * @param alpha
   *          float
   */
  public void setTint(int rgb, float alpha) {
    int c = parent.color(rgb, alpha);
    setTintColor(c);
  }

  /**
   * Set the tint color to the specified gray tone and alpha value.
   * 
   * @param gray
   *          float
   * @param alpha
   *          float
   */
  public void setTint(float gray, float alpha) {
    int c = parent.color(gray, alpha);
    setTintColor(c);
  }

  /**
   * Set the tint color to the specified color components.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   */
  public void setTint(int x, int y, int z) {
    int c = parent.color(x, y, z);
    setTintColor(c);
  }

  /**
   * Set the tint color to the specified color components.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   */
  public void setTint(float x, float y, float z) {
    int c = parent.color(x, y, z);
    setTintColor(c);
  }

  /**
   * Set the tint color to the specified color components and alpha component.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   * @param a
   *          int
   */
  public void setTint(int x, int y, int z, int a) {
    int c = parent.color(x, y, z, a);
    setTintColor(c);
  }

  /**
   * Set the tint color to the specified color components and alpha component.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   * @param a
   *          float
   */
  public void setTint(float x, float y, float z, float a) {
    int c = parent.color(x, y, z, a);
    setTintColor(c);
  }

  protected void setTintColor(int color) {
    int ir, ig, ib, ia;

    ia = (color >> 24) & 0xff;
    ir = (color >> 16) & 0xff;
    ig = (color >> 8) & 0xff;
    ib = color & 0xff;

    tintA = ia / 255.0f;
    tintR = ir / 255.0f;
    tintG = ig / 255.0f;
    tintB = ib / 255.0f;
  }

  /**
   * Set the specular color to the specified gray tone.
   * 
   * @param gray
   *          float
   */
  public void setReflection(float gray) {
    int c = parent.color(gray);
    setSpecularColor(c);
  }

  /**
   * Set the specular color to the specified gray tone and alpha value.
   * 
   * @param gray
   *          int
   * @param alpha
   *          int
   */
  public void setReflection(int gray, int alpha) {
    int c = parent.color(gray, alpha);
    setSpecularColor(c);
  }

  /**
   * Set the specular color to the specified rgb color and alpha value.
   * 
   * @param rgb
   *          int
   * @param alpha
   *          float
   */
  public void setReflection(int rgb, float alpha) {
    int c = parent.color(rgb, alpha);
    setSpecularColor(c);
  }

  /**
   * Set the specular color to the specified gray tone and alpha value.
   * 
   * @param gray
   *          float
   * @param alpha
   *          float
   */
  public void setReflection(float gray, float alpha) {
    int c = parent.color(gray, alpha);
    setSpecularColor(c);
  }

  /**
   * Set the specular color to the specified color components.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   */
  public void setReflection(int x, int y, int z) {
    int c = parent.color(x, y, z);
    setSpecularColor(c);
  }

  /**
   * Set the specular color to the specified color components.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   */
  public void setReflection(float x, float y, float z) {
    int c = parent.color(x, y, z);
    setSpecularColor(c);
  }

  /**
   * Set the specular color to the specified color components and alpha
   * component.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   * @param a
   *          int
   */
  public void setReflection(int x, int y, int z, int a) {
    int c = parent.color(x, y, z, a);
    setSpecularColor(c);
  }

  /**
   * Set the specular color to the specified color components and alpha
   * component.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   * @param a
   *          float
   */
  public void setReflection(float x, float y, float z, float a) {
    int c = parent.color(x, y, z, a);
    setSpecularColor(c);
  }

  protected void setSpecularColor(int color) {
    int ir, ig, ib, ia;

    ia = (color >> 24) & 0xff;
    ir = (color >> 16) & 0xff;
    ig = (color >> 8) & 0xff;
    ib = color & 0xff;

    specularColor[0] = ir / 255.0f;
    specularColor[1] = ig / 255.0f;
    specularColor[2] = ib / 255.0f;
    specularColor[3] = ia / 255.0f;
  }

  /**
   * Set the emissive color to the specified gray tone.
   * 
   * @param gray
   *          float
   */
  public void setEmission(float gray) {
    int c = parent.color(gray);
    setEmissiveColor(c);
  }

  /**
   * Set the emissive color to the specified gray tone and alpha value.
   * 
   * @param gray
   *          int
   * @param alpha
   *          int
   */
  public void setEmission(int gray, int alpha) {
    int c = parent.color(gray, alpha);
    setEmissiveColor(c);
  }

  /**
   * Set the emissive color to the specified rgb color and alpha value.
   * 
   * @param rgb
   *          int
   * @param alpha
   *          float
   */
  public void setEmission(int rgb, float alpha) {
    int c = parent.color(rgb, alpha);
    setEmissiveColor(c);
  }

  /**
   * Set the emissive color to the specified gray tone and alpha value.
   * 
   * @param gray
   *          float
   * @param alpha
   *          float
   */
  public void setEmission(float gray, float alpha) {
    int c = parent.color(gray, alpha);
    setEmissiveColor(c);
  }

  /**
   * Set the emissive color to the specified color components.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   */
  public void setEmission(int x, int y, int z) {
    int c = parent.color(x, y, z);
    setEmissiveColor(c);
  }

  /**
   * Set the emissive color to the specified color components.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   */
  public void setEmission(float x, float y, float z) {
    int c = parent.color(x, y, z);
    setEmissiveColor(c);
  }

  /**
   * Set the emissive color to the specified color components and alpha
   * component.
   * 
   * @param x
   *          int
   * @param y
   *          int
   * @param z
   *          int
   * @param a
   *          int
   */
  public void setEmission(int x, int y, int z, int a) {
    int c = parent.color(x, y, z, a);
    setEmissiveColor(c);
  }

  /**
   * Set the emissive color to the specified color components and alpha
   * component.
   * 
   * @param x
   *          float
   * @param y
   *          float
   * @param z
   *          float
   * @param a
   *          float
   */
  public void setEmission(float x, float y, float z, float a) {
    int c = parent.color(x, y, z, a);
    setEmissiveColor(c);
  }

  protected void setEmissiveColor(int color) {
    int ir, ig, ib, ia;

    ia = (color >> 24) & 0xff;
    ir = (color >> 16) & 0xff;
    ig = (color >> 8) & 0xff;
    ib = color & 0xff;

    emissiveColor[0] = ir / 255.0f;
    emissiveColor[1] = ig / 255.0f;
    emissiveColor[2] = ib / 255.0f;
    emissiveColor[3] = ia / 255.0f;
  }

  public void render() {
    render(0, size - 1, null);
  }

  public void render(GLModelEffect effect) {
    render(0, size - 1, effect);
  }

  public void render(int first, int last) {
    render(0, size - 1, null);
  }

  public void render(int first, int last, GLModelEffect effect) {
    if (colorsVBO == null)
      gl.glColor4f(tintR, tintG, tintB, tintA);

    // Setting specular and emissive colors.
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, specularColor, 0);
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SHININESS, shininess, 0);
    gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_EMISSION, emissiveColor, 0);

    gl.glLineWidth(lineWidth);
    if (usingPointSprites)
      gl.glPointSize(PApplet.min(pointSize, maxPointSize));
    else
      gl.glPointSize(pointSize);

    if (effect != null)
      effect.start();

    // Setting-up blending.
    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);
      // how to implement all these other blending modes:
      // else if (blendMode == LIGHTEST)
      // else if (blendMode == DIFFERENCE)
      // else if (blendMode == EXCLUSION)
      // else if (blendMode == SCREEN)
      // else if (blendMode == OVERLAY)
      // else if (blendMode == HARD_LIGHT)
      // else if (blendMode == SOFT_LIGHT)
      // else if (blendMode == DODGE)
      // else if (blendMode == BURN)
    }

    if (normCoordsVBO != null) {
      gl.glEnableClientState(GL.GL_NORMAL_ARRAY);
      gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, normCoordsVBO[0]);
      gl.glNormalPointer(GL.GL_FLOAT, 4 * BufferUtil.SIZEOF_FLOAT, 0);
    }

    if (colorsVBO != null) {
      gl.glEnableClientState(GL.GL_COLOR_ARRAY);
      gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, colorsVBO[0]);
      gl.glColorPointer(4, GL.GL_FLOAT, 0, 0);
    }

    if (texCoordsVBO != null) {
      gl.glEnable(textures[0].getTextureTarget());

      // Binding texture units.
      for (int n = 0; n < numTextures; n++) {
        gl.glActiveTexture(GL.GL_TEXTURE0 + n);
        gl.glBindTexture(GL.GL_TEXTURE_2D, textures[n].getTextureID());
      }

      if (usingPointSprites) {
        // Texturing with point sprites.

        // This is how will our point sprite's size will be modified by
        // distance from the viewer
        gl.glPointParameterfvARB(GL.GL_POINT_DISTANCE_ATTENUATION_ARB,
            ptDistAtt, 0);

        // The alpha of a point is calculated to allow the fading of points
        // instead of shrinking them past a defined threshold size. The
        // threshold
        // is defined by GL_POINT_FADE_THRESHOLD_SIZE_ARB and is not clamped to
        // the minimum and maximum point sizes.
        gl.glPointParameterfARB(GL.GL_POINT_FADE_THRESHOLD_SIZE_ARB,
            spriteFadeSize);
        gl.glPointParameterfARB(GL.GL_POINT_SIZE_MIN_ARB, 1.0f);
        gl.glPointParameterfARB(GL.GL_POINT_SIZE_MAX_ARB, maxPointSize);

        // Specify point sprite texture coordinate replacement mode for each
        // texture unit
        gl.glTexEnvf(GL.GL_POINT_SPRITE_ARB, GL.GL_COORD_REPLACE_ARB,
            GL.GL_TRUE);

        gl.glEnable(GL.GL_POINT_SPRITE_ARB);
      } else {
        // Regular texturing.
        gl.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY);
        for (int n = 0; n < numTextures; n++) {
          gl.glClientActiveTexture(GL.GL_TEXTURE0 + n);
          gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, texCoordsVBO[n]);
          gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, 0);
        }
      }

      if (effect != null)
        effect.setTextures(textures);
    }

    // Drawing the vertices:
    gl.glEnableClientState(GL.GL_VERTEX_ARRAY);

    // ...with their attributes.
    if (effect != null) {
      effect.enableVertexAttribs();
      
      effect.setVertexAttribs(attribVBO, attribName, attribSize);      
    }

    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, vertCoordsVBO[0]);

    // The vertices in the array have 4 components: x, y, z, w. If the user
    // doesn't explicity specify w, then it is set to 1 by default.
    gl.glVertexPointer(4, GL.GL_FLOAT, 0, 0);

    gl.glDrawArrays(vertexMode, first, last - first + 1);

    if (effect != null)
      effect.disableVertexAttribs();

    gl.glBindBuffer(GL.GL_ARRAY_BUFFER_ARB, 0);
    gl.glDisableClientState(GL.GL_VERTEX_ARRAY);

    if (texCoordsVBO != null) {
      if (usingPointSprites) {
        gl.glDisable(GL.GL_POINT_SPRITE_ARB);
      } else {
        for (int n = 0; n < numTextures; n++) {
          gl.glClientActiveTexture(GL.GL_TEXTURE0 + n);
          gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
        }
        gl.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY);
      }
      gl.glDisable(textures[0].getTextureTarget());
    }
    if (colorsVBO != null) {
      gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
      gl.glDisableClientState(GL.GL_COLOR_ARRAY);
    }
    if (normCoordsVBO != null) {
      gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);
      gl.glDisableClientState(GL.GL_NORMAL_ARRAY);
    }

    // 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);

    if (effect != null)
      effect.stop();
  }

  // /////////////////////////////////////////////////////////////////////////////////
  // Saving, loading from binary.

  protected void loadPVectorArrayListFromBinary(String filename,
      ArrayList<PVector> data, int nelem) {
    try {
      FileInputStream fis = new FileInputStream(parent.dataPath("") + filename);
      DataInputStream dis = new DataInputStream(fis);

      while (dis.available() > 2) {
        float x, y, z;
        x = y = z = 0;
        if (1 <= nelem)
          x = dis.readShort() / 100f;
        if (2 <= nelem)
          y = dis.readShort() / 100f;
        if (3 <= nelem)
          z = dis.readShort() / 100f;

        PVector thisLoc = new PVector(x, y, z);
        data.add(thisLoc);
      }
      fis.close();
    } catch (Exception e) {
      System.out.println("Exception: " + e);
    }
  }

  protected void savePVectorArrayListToBinary(String fileName,
      ArrayList<PVector> data, int nelem) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    for (int i = 0; i < data.size(); i++) {
      PVector loc = (PVector) data.get(i);
      try {
        if (1 <= nelem)
          dos.writeShort((short) (loc.x * 100f));
        if (2 <= nelem)
          dos.writeShort((short) (loc.y * 100f));
        if (3 <= nelem)
          dos.writeShort((short) (loc.z * 100f));
        dos.flush();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    byte[] saveBytes = baos.toByteArray();
    parent.saveBytes(fileName, saveBytes);
  }

  protected void loadFloatArrayListFromBinary(String filename,
      ArrayList<float[]> data, int nelem) {
    try {
      FileInputStream fis = new FileInputStream(parent.dataPath("") + filename);
      DataInputStream dis = new DataInputStream(fis);

      while (dis.available() > 2) {
        float[] loc = new float[nelem];
        for (int k = 0; k < nelem; k++)
          loc[k] = dis.readShort() / 100f;
        data.add(loc);
      }
      fis.close();
    } catch (Exception e) {
      System.out.println("Exception: " + e);
    }
  }

  protected void saveFloatArrayToBinary(String fileName,
      ArrayList<float[]> data, int nelem) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    for (int i = 0; i < data.size(); i++) {
      float[] loc = (float[]) data.get(i);
      try {
        for (int k = 0; k < nelem; k++)
          dos.writeShort((short) (loc[k] * 100f));
        dos.flush();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    byte[] saveBytes = baos.toByteArray();
    parent.saveBytes(fileName, saveBytes);
  }

  protected void loadFloatArrayFromBinary(String filename, float[] data,
      int nsize, int nelem, int nsave) {
    try {
      FileInputStream fis = new FileInputStream(parent.dataPath("") + filename);
      DataInputStream dis = new DataInputStream(fis);

      for (int n = 0; n < nsize; n++) {
        for (int k = 0; k < nsave; k++)
          data[nelem * n + k] = dis.readShort() / 100f;
        if (dis.available() <= 2)
          break;
      }

      fis.close();
    } catch (Exception e) {
      System.out.println("Exception: " + e);
    }
  }

  protected void saveFloatArrayToBinary(String fileName, float[] data,
      int nsize, int nelem, int nsave) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    for (int n = 0; n < nsize; n++) {
      try {
        for (int k = 0; k < nsave; k++)
          dos.writeShort((short) (data[nelem * n + k] * 100f));
        dos.flush();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    byte[] saveBytes = baos.toByteArray();
    parent.saveBytes(fileName, saveBytes);
  }

  /**
   * Sets the positions corresponding to vertex i in the tmpColorArray to the
   * specified color.
   * 
   * @param i
   *          int
   * @param color
   *          int
   */
  protected void putColorInTmpArray(int i, int color) {
    int ir, ig, ib, ia;

    if (tmpColorArray == null) {
      tmpColorArray = new float[4 * size];
      colors.get(tmpColorArray);
      colors.rewind();
    }

    ia = (color >> 24) & 0xff;
    ir = (color >> 16) & 0xff;
    ig = (color >> 8) & 0xff;
    ib = color & 0xff;

    tmpColorArray[4 * i + 0] = ir / 255.0f;
    tmpColorArray[4 * i + 1] = ig / 255.0f;
    tmpColorArray[4 * i + 2] = ib / 255.0f;
    tmpColorArray[4 * i + 3] = ia / 255.0f;
  }

  /**
   * Sets all the vertices with the specified color.
   * 
   * @param color
   *          int
   */
  protected void updateAllColors(int color) {
    int ir, ig, ib, ia;
    float fr, fg, fb, fa;

    beginUpdateColors();
    if (tmpColorArray == null) {
      tmpColorArray = new float[4 * size];
      colors.get(tmpColorArray);
      colors.rewind();
    }

    ia = (color >> 24) & 0xff;
    ir = (color >> 16) & 0xff;
    ig = (color >> 8) & 0xff;
    ib = color & 0xff;

    fa = ia / 255.0f;
    fr = ir / 255.0f;
    fg = ig / 255.0f;
    fb = ib / 255.0f;

    for (int i = 0; i < size; i++) {
      tmpColorArray[4 * i + 0] = fr;
      tmpColorArray[4 * i + 1] = fg;
      tmpColorArray[4 * i + 2] = fb;
      tmpColorArray[4 * i + 3] = fa;
    }
    endUpdateColors();
  }

  protected void initModelCommon(PApplet parent) {
    this.parent = parent;
    pgl = (PGraphicsOpenGL) parent.g;
    gl = pgl.gl;

    tintR = tintG = tintB = tintA = 1.0f;
    shininess[0] = 0.0f;

    pointSize = 1.0f;
    lineWidth = 1.0f;
    usingPointSprites = false;
    blend = false;
    blendMode = ADD;

    tmpVertArray = null;
    tmpColorArray = null;
    tmpNormalsArray = null;
    tmpTexCoordsArray = null;
    tmpAttributesArray = null;
  }

  protected void loadXML(XMLElement xml) {
    int n = xml.getChildCount();
    String name, content;
    XMLElement child;

    GLTexture[] texturesList;
    ArrayList<PVector> verticesList;
    ArrayList<PVector>[] texCoordsList;
    ArrayList<float[]>[] vertexAttribsList;
    ArrayList<PVector> normalsList;
    ArrayList<float[]> colorsList;
    String[] texNames;
    String[] attrNames;
    int[] attrSizes;

    texturesList = null;
    verticesList = new ArrayList<PVector>();
    texCoordsList = null;
    vertexAttribsList = null;
    normalsList = new ArrayList<PVector>();
    colorsList = new ArrayList<float[]>();
    texNames = null;
    attrNames = null;
    attrSizes = null;

    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("description")) {
        description = child.getContent();
      } else if (name.equals("size")) {
        size = PApplet.parseInt(child.getContent());
      } else if (name.equals("geometry")) {
        content = child.getContent();
        vertexMode = GLUtils.parsePrimitiveTypeUpperCase(content);
        if (vertexMode == GL.GL_POINTS && content.equals("POINT_SPRITES")) {
          vertexMode = GL.GL_POINTS;
          usingPointSprites = true;
          float[] tmp = { 0.0f };
          gl.glGetFloatv(GL.GL_POINT_SIZE_MAX_ARB, tmp, 0);
          maxPointSize = tmp[0];
          pointSize = maxPointSize;
          spriteFadeSize = 0.6f * pointSize;
        }
      } else if (name.equals("mode")) {
        vboUsage = GLUtils.parseVBOMode(child.getContent());
      } else if (name.equals("textures")) {
        int ntex = child.getChildCount();
        texturesList = new GLTexture[ntex];
        texNames = new String[ntex];
        texCoordsList = new ArrayList[ntex];

        loadTextures(child, texturesList, texCoordsList, texNames);
      } else if (name.equals("vertexattribs")) {
        int nattr = child.getChildCount();

        vertexAttribsList = new ArrayList[nattr];
        attrNames = new String[nattr];
        attrSizes = new int[nattr];

        loadVertexAttribs(child, vertexAttribsList, attrNames, attrSizes);
      } else if (name.equals("vertices")) {
        String binfile = child.getStringAttribute("file");
        if (binfile != null)
          loadVertices(binfile, verticesList);
        else
          loadVertices(child, verticesList);
      } else if (name.equals("texcoords")) {
        if (texCoordsList != null) {
          int unit = child.getIntAttribute("unit");
          if (texCoordsList[unit] != null) {
            String binfile = child.getStringAttribute("file");
            if (binfile != null)
              loadTexCoords(binfile, texCoordsList[unit]);
            else
              loadTexCoords(child, texCoordsList[unit]);
          }
        }
      } else if (name.equals("colors")) {
        String binfile = child.getStringAttribute("file");
        if (binfile != null)
          loadColors(binfile, colorsList);
        else
          loadColors(child, colorsList);
      } else if (name.equals("normals")) {
        String binfile = child.getStringAttribute("file");
        if (binfile != null)
          loadNormals(binfile, normalsList);
        else
          loadNormals(child, normalsList);
      } else if (name.equals("attribs")) {
        if (vertexAttribsList != null && attrSizes != null) {
          int num = child.getIntAttribute("number");
          if (vertexAttribsList[num] != null) {
            String binfile = child.getStringAttribute("file");
            if (binfile != null)
              loadVertexAttrib(binfile, vertexAttribsList[num], attrSizes[num]);
            else
              loadVertexAttrib(child, vertexAttribsList[num], attrSizes[num]);
          }
        }
      }
    }

    gl.glGenBuffersARB(1, vertCoordsVBO, 0);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, vertCoordsVBO[0]);
    gl.glBufferDataARB(GL.GL_ARRAY_BUFFER_ARB, size * 4
        * BufferUtil.SIZEOF_FLOAT, null, vboUsage);
    gl.glBindBufferARB(GL.GL_ARRAY_BUFFER_ARB, 0);

    updateVertices(verticesList);

    int ntex = texturesList.length;
    if (0 < ntex) {
      initTextures(ntex);
      GLTexture tex;
      ArrayList<PVector> tcoords;

      for (int j = 0; j < ntex; j++) {
        tex = texturesList[j];
        setTexture(j, tex);
        // It should be something like:
        // setTexture(j, tex, texNames[j]);
        // but texture names are still not used.

        tcoords = (ArrayList<PVector>) texCoordsList[j];
        if (tcoords.size() == size)
          updateTexCoords(j, tcoords);
      }
    }

    if (normalsList.size() == size) {
      initNormals();
      updateNormals(normalsList);
    }

    if (colorsList.size() == size) {
      initColors();
      updateColors(colorsList);
    }

    int nattr = vertexAttribsList.length;
    if (0 < nattr) {
      initAttributes(nattr);
      ArrayList<float[]> attribs;

      for (int j = 0; j < nattr; j++) {
        setAttribute(j, attrNames[j], attrSizes[j]);

        attribs = vertexAttribsList[j];
        updateAttributes(j, attribs);
      }
    }
  }

  protected void loadTextures(XMLElement xml, GLTexture[] texturesList,
      ArrayList<PVector>[] texCoordsList, String[] texNames) {
    int n = xml.getChildCount();
    XMLElement child;
    String name;

    String unitStr, fn;
    int unit;
    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("texture")) {
        unitStr = child.getContent();
        unit = PApplet.parseInt(unitStr);

        texCoordsList[unit] = new ArrayList<PVector>();

        texNames[unit] = child.getStringAttribute("name");
        fn = child.getStringAttribute("file");
        texturesList[unit] = new GLTexture(parent, fn);
      }
    }
  }

  protected void loadVertexAttribs(XMLElement xml,
      ArrayList<float[]>[] vertexAttribsList, String[] attrNames,
      int[] attrSizes) {
    int n = xml.getChildCount();
    XMLElement child;
    String name;

    String numStr;
    int num;
    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("vertexattrib")) {
        numStr = child.getContent();
        num = PApplet.parseInt(numStr);

        vertexAttribsList[num] = new ArrayList<float[]>();

        attrNames[num] = child.getStringAttribute("name");
        attrSizes[num] = child.getIntAttribute("size");
      }
    }
  }

  protected void loadVertices(XMLElement xml, ArrayList<PVector> verticesList) {
    int n = xml.getChildCount();
    XMLElement child;
    String name;

    String coordStr;
    float[] coord;
    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("vertex")) {
        coordStr = child.getContent();
        coord = PApplet.parseFloat(PApplet.split(coordStr, ' '));

        if (coord.length == 3)
          verticesList.add(new PVector(coord[0], coord[1], coord[2]));
      }
    }
  }

  protected void loadVertices(String binaryFN, ArrayList<PVector> verticesList) {
    loadPVectorArrayListFromBinary(binaryFN, verticesList, 3);
  }

  protected void loadTexCoords(XMLElement xml, ArrayList<PVector> texCoordsList) {
    int n = xml.getChildCount();
    XMLElement child;
    String name;

    String coordStr;
    float[] coord;
    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("texcoord")) {
        coordStr = child.getContent();
        coord = PApplet.parseFloat(PApplet.split(coordStr, ' '));

        if (coord.length == 2)
          texCoordsList.add(new PVector(coord[0], coord[1]));
      }
    }
  }

  protected void loadTexCoords(String binaryFN, ArrayList<PVector> texCoordsList) {
    loadPVectorArrayListFromBinary(binaryFN, texCoordsList, 2);
  }

  protected void loadColors(XMLElement xml, ArrayList<float[]> colorsList) {
    int n = xml.getChildCount();
    XMLElement child;
    String name;

    String coordStr;
    float[] coord;
    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("color")) {
        coordStr = child.getContent();
        coord = PApplet.parseFloat(PApplet.split(coordStr, ' '));

        if (coord.length == 4)
          colorsList.add(coord);
      }
    }
  }

  protected void loadColors(String binaryFN, ArrayList<float[]> colorsList) {
    loadFloatArrayListFromBinary(binaryFN, colorsList, 4);
  }

  protected void loadNormals(XMLElement xml, ArrayList<PVector> normalsList) {
    int n = xml.getChildCount();
    XMLElement child;
    String name;

    String coordStr;
    float[] coord;
    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("normal")) {
        coordStr = child.getContent();
        coord = PApplet.parseFloat(PApplet.split(coordStr, ' '));

        if (coord.length == 3)
          normalsList.add(new PVector(coord[0], coord[1], coord[2]));
      }
    }
  }

  protected void loadNormals(String binaryFN, ArrayList<PVector> normalsList) {
    loadPVectorArrayListFromBinary(binaryFN, normalsList, 3);
  }

  protected void loadVertexAttrib(XMLElement xml,
      ArrayList<float[]> vertexAttribsList, int attrSize) {
    int n = xml.getChildCount();
    XMLElement child;
    String name;

    String coordStr;
    float[] coord;
    for (int i = 0; i < n; i++) {
      child = xml.getChild(i);
      name = child.getName();
      if (name.equals("attrib")) {
        coordStr = child.getContent();
        coord = PApplet.parseFloat(PApplet.split(coordStr, ' '));

        if (coord.length == attrSize)
          vertexAttribsList.add(coord);
      }
    }
  }

  protected void loadVertexAttrib(String binaryFN,
      ArrayList<float[]> vertexAttribsList, int attrSize) {
    loadFloatArrayListFromBinary(binaryFN, vertexAttribsList, attrSize);
  }
}
