(PApplet.max(DEFAULT_PATHS, DEFAULT_FACES));
+ }
+
+ public void beginShape(int kind) {
+ shape = kind;
+
+ if (hints[ENABLE_DEPTH_SORT]) {
+ // TODO:
+ // Implement depth sorting with vertex arrays.
+
+ // continue with previous vertex, line and triangle count
+ // all shapes are rendered at endDraw();
+ shapeFirst = vertexCount;
+ shapeLast = 0;
+
+ } else {
+ // reset vertex, line and triangle information
+ // every shape is rendered at endShape();
+ vertexCount = 0;
+ lineCount = 0;
+ triangleCount = 0;
+ }
+
+ noTexture();
+ }
+
+ public void mergeRecord() {
+ // Setting this parameter to true has the result of A3D trying to
+ // set unique names to each shape being created between beginRecord/endRecord.
+ // In this way, even if two shapes have all of their parameters identical (mode,
+ // textures, etc), but their names are different, then they will be recorded in
+ // separate child shapes in the recorded shape.
+ mergeRecShapes = true;
+ }
+
+ public void noMergeRecord() {
+ mergeRecShapes = false;
+ }
+
+ public void shapeName(String name) {
+ recShapeName = name;
+ }
+
+ // public void edge(boolean e)
+ // public void normal(float nx, float ny, float nz)
+ // public void textureMode(int mode)
+
+ public void texture(PImage image) {
+ super.texture(image);
+ multitextureImages[0] = image;
+ java.util.Arrays.fill(multitextureImages, 1, maxTextureUnits, null);
+ numMultitextures = 1;
+ }
+
+ public void texture(PImage image0, PImage image1) {
+ if (1 < maxTextureUnits) {
+ super.texture(image0);
+ multitextureImages[0] = image0;
+ multitextureImages[1] = image1;
+ java.util.Arrays.fill(multitextureImages, 2, maxTextureUnits, null);
+ numMultitextures = 2;
+ if (numTexBuffers < 2) {
+ addTexBuffers(2 - numTexBuffers);
+ }
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void texture(PImage image0, PImage image1, PImage image2) {
+ if (2 < maxTextureUnits) {
+ super.texture(image0);
+ multitextureImages[0] = image0;
+ multitextureImages[1] = image1;
+ multitextureImages[2] = image2;
+ java.util.Arrays.fill(multitextureImages, 3, maxTextureUnits, null);
+ numMultitextures = 3;
+ if (numTexBuffers < 3) {
+ addTexBuffers(3 - numTexBuffers);
+ }
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void texture(PImage image0, PImage image1, PImage image2, PImage image3) {
+ if (3 < maxTextureUnits) {
+ super.texture(image0);
+ multitextureImages[0] = image0;
+ multitextureImages[1] = image1;
+ multitextureImages[2] = image2;
+ multitextureImages[3] = image3;
+ java.util.Arrays.fill(multitextureImages, 4, maxTextureUnits, null);
+ numMultitextures = 4;
+ if (numTexBuffers < 4) {
+ addTexBuffers(4 - numTexBuffers);
+ }
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void texture(PImage[] images) {
+ int len = images.length;
+ if (len <= maxTextureUnits) {
+ super.texture(images[0]);
+ PApplet.arrayCopy(images, 0, multitextureImages, 0, len);
+ java.util.Arrays.fill(multitextureImages, len, maxTextureUnits, null);
+ numMultitextures = len;
+ if (numTexBuffers < len) {
+ addTexBuffers(len - numTexBuffers);
+ }
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void noTexture() {
+ super.noTexture();
+ numMultitextures = 0;
+ clearMultitextures();
+ clearMultitextures0();
+ }
+
+ public void vertex(float x, float y, float u, float v) {
+ vertexTexture(u, v, 0);
+ vertex(x, y);
+ int n = vertexCount - 1;
+ for (int i = 0; i < numMultitextures; i++) {
+ vertexTex[n][i] = multitextureImages[i];
+ vertexU[n][i] = multitextureU[0];
+ vertexV[n][i] = multitextureV[0];
+ }
+ }
+
+ public void vertex(float x, float y, float u0, float v0, float u1, float v1) {
+ if (2 <= maxTextureUnits) {
+ vertexTexture(u0, v0, 0);
+ vertexTexture(u1, v1, 1);
+ vertex(x, y);
+ setMultitextureData(2);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void vertex(float x, float y, float u0, float v0, float u1, float v1, float u2, float v2) {
+ if (3 <= maxTextureUnits) {
+ vertexTexture(u0, v0, 0);
+ vertexTexture(u1, v1, 1);
+ vertexTexture(u2, v2, 2);
+ vertex(x, y);
+ setMultitextureData(3);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void vertex(float x, float y, float u0, float v0, float u1, float v1, float u2, float v2, float u3, float v3) {
+ if (4 <= maxTextureUnits) {
+ vertexTexture(u0, v0, 0);
+ vertexTexture(u1, v1, 1);
+ vertexTexture(u2, v2, 2);
+ vertexTexture(u2, v2, 3);
+ vertex(x, y);
+ setMultitextureData(4);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void vertex(float x, float y, float[] u, float[] v) {
+ int len = PApplet.min(u.length, v.length);
+ if (len <= maxTextureUnits) {
+ for (int t = 0; t < len; t++) {
+ vertexTexture(u[t], v[t], t);
+ }
+ vertex(x, y);
+ setMultitextureData(len);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void vertex(float x, float y, float z, float u, float v) {
+ vertexTexture(u, v, 0);
+ vertex(x, y, z);
+ int n = vertexCount - 1;
+ for (int i = 0; i < numMultitextures; i++) {
+ vertexTex[n][i] = multitextureImages[i];
+ vertexU[n][i] = multitextureU[0];
+ vertexV[n][i] = multitextureV[0];
+ }
+ }
+
+ public void vertex(float x, float y, float z, float u0, float v0, float u1, float v1) {
+ if (2 <= maxTextureUnits) {
+ vertexTexture(u0, v0, 0);
+ vertexTexture(u1, v1, 1);
+ vertex(x, y, z);
+ setMultitextureData(2);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void vertex(float x, float y, float z, float u0, float v0, float u1, float v1, float u2, float v2) {
+ if (3 <= maxTextureUnits) {
+ vertexTexture(u0, v0, 0);
+ vertexTexture(u1, v1, 1);
+ vertexTexture(u2, v2, 2);
+ vertex(x, y, z);
+ setMultitextureData(3);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void vertex(float x, float y, float z, float u0, float v0, float u1, float v1, float u2, float v2, float u3, float v3) {
+ if (4 <= maxTextureUnits) {
+ vertexTexture(u0, v0, 0);
+ vertexTexture(u1, v1, 1);
+ vertexTexture(u2, v2, 2);
+ vertexTexture(u2, v2, 3);
+ vertex(x, y, z);
+ setMultitextureData(4);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+ public void vertex(float x, float y, float z, float[] u, float[] v) {
+ int len = PApplet.min(u.length, v.length);
+ if (len <= maxTextureUnits) {
+ for (int t = 0; t < len; t++) {
+ vertexTexture(u[t], v[t], t);
+ }
+ vertex(x, y, z);
+ setMultitextureData(len);
+ } else {
+ System.err.println("OPENGL2: insufficient texture units.");
+ }
+ }
+
+
+ protected void vertexCheck() {
+ super.vertexCheck();
+
+ if (vertexCount == vertexTex.length) {
+ float tempu[][];
+ float tempv[][];
+ PImage tempi[][];
+
+ tempu = new float[vertexCount << 1][numTexBuffers];
+ tempv = new float[vertexCount << 1][numTexBuffers];
+ tempi = new PImage[vertexCount << 1][numTexBuffers];
+
+ for (int i = 0; i < vertexCount; i++) {
+ PApplet.arrayCopy(vertexU[i], 0, tempu[i], 0, numTexBuffers);
+ PApplet.arrayCopy(vertexV[i], 0, tempv[i], 0, numTexBuffers);
+ PApplet.arrayCopy(vertexTex[i], 0, tempi[i], 0, numTexBuffers);
+ }
+
+ vertexU = tempu;
+ vertexV = tempv;
+ vertexTex = tempi;
+ }
+ }
+
+ protected void clearMultitextures() {
+ java.util.Arrays.fill(multitextureImages, null);
+ }
+
+ protected void clearMultitextures0() {
+ java.util.Arrays.fill(multitextureImages0, null);
+ }
+
+ protected boolean diffFromMultitextures0(PImage[] images) {
+ if (1 < numMultitextures) {
+ for (int i = 0; i < numMultitextures; i++) {
+ if (multitextureImages0[i] != images[i]) {
+ return true;
+ }
+ }
+ return false;
+ } else if (0 < numMultitextures) {
+ return multitextureImages0[0] != images[0];
+ } else {
+ return multitextureImages0[0] != null;
+ }
+ }
+
+ protected void setMultitextures0(PImage[] images) {
+ if (1 < numMultitextures) {
+ PApplet.arrayCopy(images, 0, multitextureImages0, 0, numMultitextures);
+ } else if (0 < numMultitextures) {
+ multitextureImages0[0] = images[0];
+ } else {
+ multitextureImages0[0] = null;
+ }
+ }
+
+ protected void vertexTexture(float u, float v, int t) {
+ if (t == 0) {
+ super.vertexTexture(u, v);
+ multitextureU[0] = textureU;
+ multitextureV[0] = textureV;
+ } else {
+ PImage img = multitextureImages[t];
+ if (img == null) {
+ throw new RuntimeException("You must first call texture() before " +
+ "using u and v coordinates with vertex()");
+ }
+ if (textureMode == IMAGE) {
+ u /= (float) img.width;
+ v /= (float) img.height;
+ }
+
+ multitextureU[t] = u;
+ multitextureV[t] = v;
+ }
+ }
+
+ protected void addTexBuffers(int more) {
+ int size = texCoordBuffer[numTexBuffers - 1].capacity();
+ for (int i = 0; i < more; i++) {
+ ByteBuffer tbb = ByteBuffer.allocateDirect(size * SIZEOF_FLOAT);
+ tbb.order(ByteOrder.nativeOrder());
+ texCoordBuffer[numTexBuffers + i] = tbb.asFloatBuffer();
+ }
+
+ texCoordArray = new float[numTexBuffers + more][size];
+
+ // Adding room for additional texture units to the U, V and vertex texture arrays.
+ // However, we need to preserve the information already stored in them, that's why
+ // the temporal arrays and the copy.
+ size = vertexTex.length;
+ float tempu[][] = new float[size][numTexBuffers + more];
+ float tempv[][] = new float[size][numTexBuffers + more];
+ PImage tempi[][] = new PImage[size][numTexBuffers + more];
+
+ for (int i = 0; i < size; i++) {
+ PApplet.arrayCopy(vertexU[i], 0, tempu[i], 0, numTexBuffers);
+ PApplet.arrayCopy(vertexV[i], 0, tempv[i], 0, numTexBuffers);
+ PApplet.arrayCopy(vertexTex[i], 0, tempi[i], 0, numTexBuffers);
+ }
+
+ vertexU = tempu;
+ vertexV = tempv;
+ vertexTex = tempi;
+
+ numTexBuffers += more;
+ }
+
+ protected void setMultitextureData(int ntex) {
+ if (numTexBuffers < ntex) {
+ addTexBuffers(ntex - numTexBuffers);
+ }
+
+ int n = vertexCount - 1;
+ PApplet.arrayCopy(multitextureU, 0, vertexU[n], 0, ntex);
+ PApplet.arrayCopy(multitextureV, 0, vertexV[n], 0, ntex);
+ PApplet.arrayCopy(multitextureImages, 0, vertexTex[n], 0, ntex);
+ }
+
+ // public void breakShape()
+
+ // public void endShape()
+
+ public void endShape(int mode) {
+ shapeLast = vertexCount;
+
+ // don't try to draw if there are no vertices
+ // (fixes a bug in LINE_LOOP that re-adds a nonexistent vertex)
+ if (vertexCount == 0) {
+ shape = 0;
+ return;
+ }
+
+ if (stroke) {
+ endShapeStroke(mode);
+ }
+
+ if (fill) {
+ endShapeFill();
+ }
+
+ // render shape and fill here if not saving the shapes for later
+ // if true, the shapes will be rendered on endDraw
+ if (!hints[ENABLE_DEPTH_SORT]) {
+ if (fill) {
+ renderTriangles(0, faceCount);
+ if (raw != null) {
+ // rawTriangles(0, triangleCount);
+ }
+ faceCount = 0;
+ vertexCount = 0;
+ triangleCount = 0;
+ }
+ if (stroke) {
+ renderLines(0, pathCount);
+ if (raw != null) {
+ // rawLines(0, lineCount);
+ }
+ lineCount = 0;
+ }
+
+ pathCount = 0;
+ faceCount = 0;
+ }
+
+ shape = 0;
+ }
+
+ protected void endShapeStroke(int mode) {
+ switch (shape) {
+ case POINTS: {
+ int stop = shapeLast;
+ for (int i = shapeFirst; i < stop; i++) {
+ addLineBreak(); // total overkill for points
+ addLine(i, i);
+ }
+ }
+ break;
+
+ case LINES: {
+ // store index of first vertex
+ int first = lineCount;
+ int stop = shapeLast - 1;
+ // increment = (shape == LINES) ? 2 : 1;
+
+ // for LINE_STRIP and LINE_LOOP, make this all one path
+ if (shape != LINES)
+ addLineBreak();
+
+ for (int i = shapeFirst; i < stop; i += 2) {
+ // for LINES, make a new path for each segment
+ if (shape == LINES)
+ addLineBreak();
+ addLine(i, i + 1);
+ }
+
+ // for LINE_LOOP, close the loop with a final segment
+ // if (shape == LINE_LOOP) {
+ if (mode == CLOSE) {
+ addLine(stop, lines[first][VERTEX1]);
+ }
+ }
+ break;
+
+ case TRIANGLES: {
+ for (int i = shapeFirst; i < shapeLast - 2; i += 3) {
+ addLineBreak();
+ // counter = i - vertex_start;
+ addLine(i + 0, i + 1);
+ addLine(i + 1, i + 2);
+ addLine(i + 2, i + 0);
+ }
+ }
+ break;
+
+ case TRIANGLE_STRIP: {
+ // first draw all vertices as a line strip
+ int stop = shapeLast - 1;
+
+ addLineBreak();
+ for (int i = shapeFirst; i < stop; i++) {
+ // counter = i - vertex_start;
+ addLine(i, i + 1);
+ }
+
+ // then draw from vertex (n) to (n+2)
+ stop = shapeLast - 2;
+ for (int i = shapeFirst; i < stop; i++) {
+ addLineBreak();
+ addLine(i, i + 2);
+ }
+ }
+ break;
+
+ case TRIANGLE_FAN: {
+ // this just draws a series of line segments
+ // from the center to each exterior point
+ for (int i = shapeFirst + 1; i < shapeLast; i++) {
+ addLineBreak();
+ addLine(shapeFirst, i);
+ }
+
+ // then a single line loop around the outside.
+ addLineBreak();
+ for (int i = shapeFirst + 1; i < shapeLast - 1; i++) {
+ addLine(i, i + 1);
+ }
+ // closing the loop
+ addLine(shapeLast - 1, shapeFirst + 1);
+ }
+ break;
+
+ case QUADS: {
+ for (int i = shapeFirst; i < shapeLast; i += 4) {
+ addLineBreak();
+ // counter = i - vertex_start;
+ addLine(i + 0, i + 1);
+ addLine(i + 1, i + 2);
+ addLine(i + 2, i + 3);
+ addLine(i + 3, i + 0);
+ }
+ }
+ break;
+
+ case QUAD_STRIP: {
+ for (int i = shapeFirst; i < shapeLast - 3; i += 2) {
+ addLineBreak();
+ addLine(i + 0, i + 2);
+ addLine(i + 2, i + 3);
+ addLine(i + 3, i + 1);
+ addLine(i + 1, i + 0);
+ }
+ }
+ break;
+
+ case POLYGON: {
+ // store index of first vertex
+ int stop = shapeLast - 1;
+
+ addLineBreak();
+ for (int i = shapeFirst; i < stop; i++) {
+ addLine(i, i + 1);
+ }
+ if (mode == CLOSE) {
+ // draw the last line connecting back to the first point in poly
+ addLine(stop, shapeFirst); // lines[first][VERTEX1]);
+ }
+ }
+ break;
+ }
+ }
+
+ protected void endShapeFill() {
+ switch (shape) {
+ case TRIANGLE_FAN: {
+ int stop = shapeLast - 1;
+ for (int i = shapeFirst + 1; i < stop; i++) {
+ addTriangle(shapeFirst, i, i + 1);
+ }
+ }
+ break;
+
+ case TRIANGLES: {
+ int stop = shapeLast - 2;
+ for (int i = shapeFirst; i < stop; i += 3) {
+ // have to switch between clockwise/counter-clockwise
+ // otherwise the feller is backwards and renderer won't draw
+ if ((i % 2) == 0) {
+ addTriangle(i, i + 2, i + 1);
+ } else {
+ addTriangle(i, i + 1, i + 2);
+ }
+ }
+ }
+ break;
+
+ case TRIANGLE_STRIP: {
+ int stop = shapeLast - 2;
+ for (int i = shapeFirst; i < stop; i++) {
+ // have to switch between clockwise/counter-clockwise
+ // otherwise the feller is backwards and renderer won't draw
+ if ((i % 2) == 0) {
+ addTriangle(i, i + 2, i + 1);
+ } else {
+ addTriangle(i, i + 1, i + 2);
+ }
+ }
+ }
+ break;
+
+ case QUADS: {
+ int stop = vertexCount - 3;
+ for (int i = shapeFirst; i < stop; i += 4) {
+ // first triangle
+ addTriangle(i, i + 1, i + 2);
+ // second triangle
+ addTriangle(i, i + 2, i + 3);
+ }
+ }
+ break;
+
+ case QUAD_STRIP: {
+ int stop = vertexCount - 3;
+ for (int i = shapeFirst; i < stop; i += 2) {
+ // first triangle
+ addTriangle(i + 0, i + 2, i + 1);
+ // second triangle
+ addTriangle(i + 2, i + 3, i + 1);
+ }
+ }
+ break;
+
+ case POLYGON: {
+ addPolygonTriangles();
+ }
+ break;
+ }
+ }
+
+ public void endRecord() {
+ if (recordingShape) {
+ if (0 < recordedVertices.size()) {
+ recordedShape.initShape(recordedVertices.size());
+ }
+ endShapeRecorderImpl(recordedShape);
+ recordedShape = null;
+ } else {
+ System.err.println("OPENGL2: Start recording with beginRecord().");
+ }
+ }
+
+ protected PShape3D endShapeRecorder() {
+ return endShapeRecorder(OPEN);
+ }
+
+ protected PShape3D endShapeRecorder(int mode) {
+ endShape(mode);
+ PShape3D shape = null;
+ if (0 < recordedVertices.size()) {
+ shape = new PShape3D(parent, recordedVertices.size());
+ }
+ endShapeRecorderImpl(shape);
+ return shape;
+ }
+
+ protected PShape3D endShapesRecorder() {
+ if (recordingShape) {
+ PShape3D shape = null;
+ if (0 < recordedVertices.size()) {
+ shape = new PShape3D(parent, recordedVertices.size());
+ }
+ endShapeRecorderImpl(shape);
+ return shape;
+ } else {
+ System.err.println("OPENGL2: Start recording with beginShapesRecorder().");
+ return null;
+ }
+ }
+
+ protected void endShapeRecorderImpl(PShape3D shape) {
+ recordingShape = false;
+ if (0 < recordedVertices.size() && shape != null) {
+
+ shape.setVertices(recordedVertices);
+ shape.setColors(recordedColors);
+ shape.setNormals(recordedNormals);
+
+ // We set children first because they contain the textures...
+ shape.optimizeChildren(recordedChildren); // (we make sure that there are not superfluous shapes)
+ shape.setChildren(recordedChildren);
+
+ // ... and then the texture coordinates.
+ for (int t = 0; t < numRecordedTextures; t++) {
+ shape.setTexcoords(t, recordedTexCoords[t]);
+ }
+
+ // Releasing memory.
+ recordedVertices.clear();
+ recordedColors.clear();
+ recordedNormals.clear();
+ for (int t = 0; t < maxTextureUnits; t++) {
+ recordedTexCoords[t].clear();
+ }
+ recordedChildren.clear();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // PSHAPE RENDERING IN 3D
+
+ public void shape(PShape shape, float x, float y, float z) {
+ if (shape.isVisible()) { // don't do expensive matrix ops if invisible
+ pushMatrix();
+
+ if (shapeMode == CENTER) {
+ translate(x - shape.getWidth() / 2, y - shape.getHeight() / 2, z
+ - shape.getDepth() / 2);
+
+ } else if ((shapeMode == CORNER) || (shapeMode == CORNERS)) {
+ translate(x, y, z);
+ }
+ shape.draw(this);
+
+ popMatrix();
+ }
+ }
+
+ public void shape(PShape shape, float x, float y, float z, float c, float d,
+ float e) {
+ if (shape.isVisible()) { // don't do expensive matrix ops if invisible
+ pushMatrix();
+
+ if (shapeMode == CENTER) {
+ // x, y and z are center, c, d and e refer to a diameter
+ translate(x - c / 2f, y - d / 2f, z - e / 2f);
+ scale(c / shape.getWidth(), d / shape.getHeight(), e / shape.getDepth());
+
+ } else if (shapeMode == CORNER) {
+ translate(x, y, z);
+ scale(c / shape.getWidth(), d / shape.getHeight(), e / shape.getDepth());
+
+ } else if (shapeMode == CORNERS) {
+ // c, d, e are x2/y2/z2, make them into width/height/depth
+ c -= x;
+ d -= y;
+ e -= z;
+ // then same as above
+ translate(x, y, z);
+ scale(c / shape.getWidth(), d / shape.getHeight(), e / shape.getDepth());
+ }
+ shape.draw(this);
+
+ popMatrix();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // BEZIER CURVE VERTICES
+
+ // TODO it seem there are no evaluators in OpenGL ES
+
+ // protected void bezierVertexCheck();
+ // public void bezierVertex(float x2, float y2,
+ // float x3, float y3,
+ // float x4, float y4)
+ // public void bezierVertex(float x2, float y2, float z2,
+ // float x3, float y3, float z3,
+ // float x4, float y4, float z4)
+
+ //////////////////////////////////////////////////////////////
+
+ // CATMULL-ROM CURVE VERTICES
+
+ // TODO it seem there are no evaluators in OpenGL ES
+
+ // protected void curveVertexCheck();
+ // public void curveVertex(float x, float y)
+ // public void curveVertex(float x, float y, float z)
+ // protected void curveVertexSegment(float x1, float y1,
+ // float x2, float y2,
+ // float x3, float y3,
+ // float x4, float y4)
+ // protected void curveVertexSegment(float x1, float y1, float z1,
+ // float x2, float y2, float z2,
+ // float x3, float y3, float z3,
+ // float x4, float y4, float z4)
+
+ //////////////////////////////////////////////////////////////
+
+ // POINTS (override from P3D)
+
+ protected void renderPoints(int start, int stop) {
+ gl2f.glEnableClientState(GL2.GL_VERTEX_ARRAY);
+ gl2f.glEnableClientState(GL2.GL_COLOR_ARRAY);
+
+ // Division by three needed because each int element in the buffer is used
+ // to store three coordinates.
+ int size = 3 * (stop - start);
+ while (vertexBuffer.capacity() / 3 < size) {
+ expandBuffers();
+ }
+
+ float sw = vertices[lines[start][VERTEX1]][SW];
+ if (sw > 0) {
+ gl2f.glPointSize(sw); // can only be set outside glBegin/glEnd
+
+ vertexBuffer.position(0);
+ colorBuffer.position(0);
+
+ int n = 0;
+ for (int i = start; i < stop; i++) {
+ float[] a = vertices[points[i][VERTEX1]];
+ vertexArray[3 * n + 0] = a[X];
+ vertexArray[3 * n + 1] = a[Y];
+ vertexArray[3 * n + 2] = a[Z];
+ colorArray[4 * n + 0] = a[SR];
+ colorArray[4 * n + 1] = a[SG];
+ colorArray[4 * n + 2] = a[SB];
+ colorArray[4 * n + 3] = a[SA];
+ n++;
+ }
+
+ vertexBuffer.put(vertexArray);
+ colorBuffer.put(colorArray);
+
+ vertexBuffer.position(0);
+ colorBuffer.position(0);
+
+ gl2f.glVertexPointer(3, GL.GL_FLOAT, 0, vertexBuffer);
+ gl2f.glColorPointer(4, GL.GL_FLOAT, 0, colorBuffer);
+ gl2f.glDrawArrays(GL.GL_POINTS, start, stop - start);
+ }
+
+ gl2f.glDisableClientState(GL2.GL_VERTEX_ARRAY);
+ gl2f.glDisableClientState(GL2.GL_COLOR_ARRAY);
+ }
+
+ // protected void rawPoints(int start, int stop) // PGraphics3D
+
+ //////////////////////////////////////////////////////////////
+
+ // LINES (override from P3D)
+
+ // protected final void addLineBreak() // PGraphics3D
+
+ /**
+ * Begin a new section of stroked geometry.
+ */
+ protected final void addLineBreak() {
+ if (pathCount == pathOffset.length) {
+ pathOffset = PApplet.expand(pathOffset);
+ pathLength = PApplet.expand(pathLength);
+ }
+ pathOffset[pathCount] = lineCount;
+ pathLength[pathCount] = 0;
+ pathCount++;
+ }
+
+ /**
+ * Add this line.
+ */
+ protected void addLine(int a, int b) {
+ if (lineCount == lines.length) {
+ int temp[][] = new int[lineCount << 1][LINE_FIELD_COUNT];
+ PApplet.arrayCopy(lines, 0, temp, 0, lineCount);
+ lines = temp;
+ }
+ lines[lineCount][VERTEX1] = a;
+ lines[lineCount][VERTEX2] = b;
+
+ // lines[lineCount][STROKE_MODE] = strokeCap | strokeJoin;
+ // lines[lineCount][STROKE_WEIGHT] = (int) (strokeWeight + 0.5f); // hmm
+ lineCount++;
+
+ // mark this piece as being part of the current path
+ pathLength[pathCount - 1]++;
+ }
+
+ /**
+ * In the current implementation, start and stop are ignored (in OpenGL). This
+ * will obviously have to be revisited if/when proper depth sorting is
+ * implemented.
+ */
+ protected void renderLines(int start, int stop) {
+ report("render_lines in");
+
+ float sw0 = 0;
+
+ gl2f.glEnableClientState(GL2.GL_VERTEX_ARRAY);
+ gl2f.glEnableClientState(GL2.GL_COLOR_ARRAY);
+
+ for (int j = start; j < stop; j++) {
+ int i = pathOffset[j];
+ float sw = vertices[lines[i][VERTEX1]][SW];
+ // report("render_lines 1");
+ // stroke weight zero will cause a gl error
+ if (sw > 0) {
+ gl2f.glLineWidth(sw);
+ if (sw0 != sw && recordingShape) {
+ // Add new vertex group.
+
+ int n0 = recordedVertices.size();
+ // The final index is n0 + pathLength[j] and not n0 + pathLength[j] -1
+ // because of the first point added before the loop (see below).
+ int n1 = n0 + pathLength[j];
+ // Identifying where this group should end (when stroke length
+ // changes).
+ for (int k = j + 1; k < stop; k++) {
+ int i1 = pathOffset[k];
+ float sw1 = vertices[lines[i1][VERTEX1]][SW];
+ if (sw0 != sw1) {
+ break;
+ }
+ n1 = n0 + pathLength[k];
+ }
+
+ String name = "shape";
+ if (mergeRecShapes) {
+ name = "shape";
+ } else {
+ name = recShapeName.equals("") ? "shape:" + recordedChildren.size() : recShapeName;
+ }
+ PShape3D child = (PShape3D)PShape3D.createChild(name, n0, n1, LINE_STRIP, sw, null);
+ recordedChildren.add(child);
+ }
+
+ // Division by three needed because each int element in the buffer is
+ // used to store three coordinates.
+ int size = 3 * (pathLength[j] + 1);
+ while (vertexBuffer.capacity() / 3 < size) {
+ expandBuffers();
+ }
+
+ vertexBuffer.position(0);
+ colorBuffer.position(0);
+
+ int n = 0;
+
+ // always draw a first point
+ float a[] = vertices[lines[i][VERTEX1]];
+ if (recordingShape) {
+ recordedVertices.add(new PVector(a[X], a[Y], a[Z]));
+ recordedColors.add(new float[] { a[SR], a[SG], a[SB], a[SA] });
+ recordedNormals.add(new PVector(0, 0, 0));
+ // We need to add texture coordinate values for all the recorded vertices and all
+ // texture units because even if this part of the recording doesn't use textures,
+ // a subsequent (previous) portion might (did), and when setting the texture coordinates
+ // for a shape we need to provide coordinates for the whole shape.
+ for (int t = 0; t < maxTextureUnits; t++) {
+ recordedTexCoords[t].add(new PVector(0, 0, 0));
+ }
+ } else {
+ vertexArray[3 * n + 0] = a[X];
+ vertexArray[3 * n + 1] = a[Y];
+ vertexArray[3 * n + 2] = a[Z];
+ colorArray[4 * n + 0] = a[SR];
+ colorArray[4 * n + 1] = a[SG];
+ colorArray[4 * n + 2] = a[SB];
+ colorArray[4 * n + 3] = a[SA];
+ n++;
+ }
+
+ // on this and subsequent lines, only draw the second point
+ for (int k = 0; k < pathLength[j]; k++) {
+ float b[] = vertices[lines[i][VERTEX2]];
+
+ if (recordingShape) {
+ recordedVertices.add(new PVector(b[X], b[Y], b[Z]));
+ recordedColors.add(new float[] { b[SR], b[SG], b[SB], b[SA] });
+ recordedNormals.add(new PVector(0, 0, 0));
+ for (int t = 0; t < maxTextureUnits; t++) {
+ recordedTexCoords[t].add(new PVector(0, 0, 0));
+ }
+ } else {
+ vertexArray[3 * n + 0] = b[X];
+ vertexArray[3 * n + 1] = b[Y];
+ vertexArray[3 * n + 2] = b[Z];
+ colorArray[4 * n + 0] = b[SR];
+ colorArray[4 * n + 1] = b[SG];
+ colorArray[4 * n + 2] = b[SB];
+ colorArray[4 * n + 3] = b[SA];
+ n++;
+ }
+
+ i++;
+ }
+
+ if (!recordingShape) {
+ vertexBuffer.put(vertexArray);
+ colorBuffer.put(colorArray);
+
+ vertexBuffer.position(0);
+ colorBuffer.position(0);
+
+ gl2f.glVertexPointer(3, GL.GL_FLOAT, 0, vertexBuffer);
+ gl2f.glColorPointer(4, GL.GL_FLOAT, 0, colorBuffer);
+ gl2f.glDrawArrays(GL.GL_LINE_STRIP, 0, pathLength[j] + 1);
+ }
+
+ }
+ sw0 = sw;
+ }
+
+ gl2f.glDisableClientState(GL2.GL_VERTEX_ARRAY);
+ gl2f.glDisableClientState(GL2.GL_COLOR_ARRAY);
+ report("render_lines out");
+
+ }
+
+ // protected void rawLines(int start, int stop)
+
+ //////////////////////////////////////////////////////////////
+
+ // TRIANGLES
+
+ /**
+ * Add the triangle.
+ */
+ protected void addTriangle(int a, int b, int c) {
+ if (triangleCount == triangles.length) {
+ int temp[][] = new int[triangleCount << 1][TRIANGLE_FIELD_COUNT];
+ PApplet.arrayCopy(triangles, 0, temp, 0, triangleCount);
+ triangles = temp;
+ }
+
+ triangles[triangleCount][VERTEX1] = a;
+ triangles[triangleCount][VERTEX2] = b;
+ triangles[triangleCount][VERTEX3] = c;
+
+ PImage[] images;
+ images = vertexTex[a];
+ boolean firstFace = triangleCount == 0;
+ if (diffFromMultitextures0(images) || firstFace) {
+ // A new face starts at the first triangle or when the texture changes.
+ addNewFace(firstFace, images);
+ } else {
+ // mark this triangle as being part of the current face.
+ faceLength[faceCount - 1]++;
+ }
+ triangleCount++;
+ setMultitextures0(images);
+ }
+
+ // New "face" starts. A face is just a range of consecutive triangles
+ // with the same textures applied to them (they could be null).
+ protected void addNewFace(boolean firstFace, PImage[] images) {
+ if (faceCount == faceOffset.length) {
+ faceOffset = PApplet.expand(faceOffset);
+ faceLength = PApplet.expand(faceLength);
+
+ PImage tempi[][] = new PImage[faceCount << 1][MAX_TEXTURES];
+ PApplet.arrayCopy(faceTextures, 0, tempi, 0, faceCount);
+ faceTextures = tempi;
+ }
+ faceOffset[faceCount] = firstFace ? 0 : triangleCount;
+ faceLength[faceCount] = 1;
+
+ PImage p[] = faceTextures[faceCount];
+ if (1 < numMultitextures) {
+ PApplet.arrayCopy(images, 0, p, 0, numMultitextures);
+ } if (0 < numMultitextures) {
+ p[0] = images[0];
+ } else {
+ // No textures in use, but images[0] might be non null because of a
+ // previous textured shape.
+ p[0] = null;
+ }
+ java.util.Arrays.fill(p, numMultitextures, maxTextureUnits, null);
+
+ faceCount++;
+ }
+
+
+ protected void renderTriangles(int start, int stop) {
+ report("render_triangles in");
+
+ int numTextures = 0;
+
+ gl2f.glEnableClientState(GL2.GL_VERTEX_ARRAY);
+ gl2f.glEnableClientState(GL2.GL_COLOR_ARRAY);
+ gl2f.glEnableClientState(GL2.GL_NORMAL_ARRAY);
+
+ for (int j = start; j < stop; j++) {
+ int i = faceOffset[j];
+
+ PImage[] images = faceTextures[j];
+ if (1 < numMultitextures) {
+ for (int t = 0; t < numMultitextures; t++) {
+ if (images[t] != null) {
+ PTexture tex = getTexture(images[t]);
+ if (tex == null) {
+ break;
+ }
+
+ gl.glEnable(tex.getGLTarget());
+ gl.glActiveTexture(GL.GL_TEXTURE0 + t);
+ gl.glBindTexture(tex.getGLTarget(), tex.getGLID());
+ renderTextures[numTextures] = tex;
+ numTextures++;
+ } else {
+ // If there is a null texture image at some point in the
+ // list, all subsequent images are ignored. This situation
+ // corresponds to a wrong multitexture specification by
+ // the user, anyways.
+ break;
+ }
+ }
+ } else if (images[0] != null) {
+ PTexture tex = getTexture(images[0]);
+ if (tex != null) {
+ gl.glEnable(tex.getGLTarget());
+ gl.glActiveTexture(GL.GL_TEXTURE0);
+ gl.glBindTexture(tex.getGLTarget(), tex.getGLID());
+ renderTextures[0] = tex;
+ numTextures = 1;
+ }
+ }
+
+ if (0 < numTextures) {
+ if (numTexBuffers < numTextures) {
+ addTexBuffers(numTextures - numTexBuffers);
+ }
+ gl2f.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+ if (1 < numTextures) {
+ setMultitextureBlend(renderTextures, numTextures);
+ }
+ }
+
+ if (recordingShape) {
+ numRecordedTextures = PApplet.max(numRecordedTextures, numTextures);
+
+ int n0 = recordedVertices.size();
+ int n1 = n0 + 3 * faceLength[j] - 1;
+
+ String name = "shape";
+ if (mergeRecShapes) {
+ name = "shape";
+ } else {
+ name = recShapeName.equals("") ? "shape:" + recordedChildren.size() : recShapeName;
+ }
+ PShape3D child = (PShape3D)PShape3D.createChild(name, n0, n1, TRIANGLES, 0, images);
+ recordedChildren.add(child);
+ }
+
+ // Division by three needed because each int element in the buffer is used
+ // to store three coordinates.
+ int size = 3 * faceLength[j];
+ while (vertexBuffer.capacity() / 3 < size) {
+ expandBuffers();
+ }
+
+ vertexBuffer.position(0);
+ colorBuffer.position(0);
+ normalBuffer.position(0);
+ for (int t = 0; t < numTextures; t++) {
+ texCoordBuffer[t].position(0);
+ }
+
+ int n = 0;
+ for (int k = 0; k < faceLength[j]; k++) {
+ int na = triangles[i][VERTEX1];
+ int nb = triangles[i][VERTEX2];
+ int nc = triangles[i][VERTEX3];
+ float a[] = vertices[na];
+ float b[] = vertices[nb];
+ float c[] = vertices[nc];
+
+ if (autoNormal && (a[HAS_NORMAL] == 0 || b[HAS_NORMAL] == 0 || c[HAS_NORMAL] == 0)) {
+ // Ok, some of the vertices defining the current triangle have not been
+ // assigned a normal, and the automatic normal calculation is enabled, so
+ // we generate the normal for all the vertices of this triangle.
+
+ // Assuming CW vertex ordering, so the outside direction for this triangle
+ // should be given by the cross product (b - a) x (b - c):
+ float x1 = b[X] - a[X];
+ float y1 = b[Y] - a[Y];
+ float z1 = b[Z] - a[Z];
+
+ float x2 = b[X] - c[X];
+ float y2 = b[Y] - c[Y];
+ float z2 = b[Z] - c[Z];
+
+ float cx = y1 * z2 - y2 * z1;
+ float cy = z1 * x2 - z2 * x1;
+ float cz = x1 * y2 - x2 * y1;
+
+ float norm = PApplet.sqrt(cx * cx + cy * cy + cz * cz);
+
+ cx /= norm;
+ cy /= norm;
+ cz /= norm;
+
+ // Same normal vector assigned to the three vertices:
+ a[NX] = b[NX] = c[NX] = cx;
+ a[NY] = b[NY] = c[NY] = cy;
+ a[NZ] = b[NZ] = c[NZ] = cz;
+
+ a[HAS_NORMAL] = b[HAS_NORMAL] = c[HAS_NORMAL] = 1;
+ }
+
+ if (numTextures == 1) {
+ float uscale = 1.0f;
+ float vscale = 1.0f;
+ float cx = 0.0f;
+ float sx = +1.0f;
+ float cy = 0.0f;
+ float sy = +1.0f;
+
+ PTexture tex = renderTextures[0];
+ uscale *= tex.getMaxTexCoordU();
+ vscale *= tex.getMaxTexCoordV();
+
+ if (tex.isFlippedX()) {
+ cx = 1.0f;
+ sx = -1.0f;
+ }
+
+ if (tex.isFlippedY()) {
+ cy = 1.0f;
+ sy = -1.0f;
+ }
+
+ // No multitexturing, so getting the texture coordinates
+ // directly from the U, V fields in the vertices array.
+ renderUa[0] = (cx + sx * a[U]) * uscale;
+ renderVa[0] = (cy + sy * a[V]) * vscale;
+
+ renderUb[0] = (cx + sx * b[U]) * uscale;
+ renderVb[0] = (cy + sy * b[V]) * vscale;
+
+ renderUc[0] = (cx + sx * c[U]) * uscale;
+ renderVc[0] = (cy + sy * c[V]) * vscale;
+ } else if (1 < numTextures) {
+ for (int t = 0; t < numTextures; t++) {
+ float uscale = 1.0f;
+ float vscale = 1.0f;
+ float cx = 0.0f;
+ float sx = +1.0f;
+ float cy = 0.0f;
+ float sy = +1.0f;
+
+ PTexture tex = renderTextures[t];
+ uscale *= tex.getMaxTexCoordU();
+ vscale *= tex.getMaxTexCoordV();
+
+ if (tex.isFlippedX()) {
+ cx = 1.0f;
+ sx = -1.0f;
+ }
+
+ if (tex.isFlippedY()) {
+ cy = 1.0f;
+ sy = -1.0f;
+ }
+
+ // The texture coordinates are obtained from the vertexU, vertexV
+ // arrays that store multitexture U, V coordinates.
+ renderUa[t] = (cx + sx * vertexU[na][t]) * uscale;
+ renderVa[t] = (cy + sy * vertexV[na][t]) * vscale;
+
+ renderUb[t] = (cx + sx * vertexU[nb][t]) * uscale;
+ renderVb[t] = (cy + sy * vertexV[nb][t]) * vscale;
+
+ renderUc[t] = (cx + sx * vertexU[nc][t]) * uscale;
+ renderVc[t] = (cy + sy * vertexV[nc][t]) * vscale;
+ }
+ }
+
+ // Adding vertex A.
+ if (recordingShape) {
+ recordedVertices.add(new PVector(a[X], a[Y], a[Z]));
+ recordedColors.add(new float[] { a[R], a[G], a[B], a[A] });
+ recordedNormals.add(new PVector(a[NX], a[NY], a[NZ]));
+ for (int t = 0; t < numTextures; t++) {
+ recordedTexCoords[t].add(new PVector(vertexU[na][t], vertexV[na][t], 0.0f));
+ }
+ // We need to add texture coordinate values for all the recorded vertices and all
+ // texture units because even if this part of the recording doesn't use textures,
+ // a subsequent (previous) portion might (did), and when setting the texture coordinates
+ // for a shape we need to provide coordinates for the whole shape.
+ for (int t = numTextures; t < maxTextureUnits; t++) {
+ recordedTexCoords[t].add(new PVector(0.0f, 0.0f, 0.0f));
+ }
+ } else {
+ vertexArray[3 * n + 0] = a[X];
+ vertexArray[3 * n + 1] = a[Y];
+ vertexArray[3 * n + 2] = a[Z];
+ colorArray[4 * n + 0] = a[R];
+ colorArray[4 * n + 1] = a[G];
+ colorArray[4 * n + 2] = a[B];
+ colorArray[4 * n + 3] = a[A];
+ normalArray[3 * n + 0] = a[NX];
+ normalArray[3 * n + 1] = a[NY];
+ normalArray[3 * n + 2] = a[NZ];
+ for (int t = 0; t < numTextures; t++) {
+ texCoordArray[t][2 * n + 0] = renderUa[t];
+ texCoordArray[t][2 * n + 1] = renderVa[t];
+ }
+ n++;
+ }
+
+ // Adding vertex B.
+ if (recordingShape) {
+ recordedVertices.add(new PVector(b[X], b[Y], b[Z]));
+ recordedColors.add(new float[] { b[R], b[G], b[B], b[A] });
+ recordedNormals.add(new PVector(b[NX], b[NY], b[NZ]));
+ for (int t = 0; t < numTextures; t++) {
+ recordedTexCoords[t].add(new PVector(vertexU[nb][t], vertexV[nb][t], 0.0f));
+ }
+ // Idem to comment in section corresponding to vertex A.
+ for (int t = numTextures; t < maxTextureUnits; t++) {
+ recordedTexCoords[t].add(new PVector(0.0f, 0.0f, 0.0f));
+ }
+ } else {
+ vertexArray[3 * n + 0] = b[X];
+ vertexArray[3 * n + 1] = b[Y];
+ vertexArray[3 * n + 2] = b[Z];
+ colorArray[4 * n + 0] = b[R];
+ colorArray[4 * n + 1] = b[G];
+ colorArray[4 * n + 2] = b[B];
+ colorArray[4 * n + 3] = b[A];
+ normalArray[3 * n + 0] = b[NX];
+ normalArray[3 * n + 1] = b[NY];
+ normalArray[3 * n + 2] = b[NZ];
+ for (int t = 0; t < numTextures; t++) {
+ texCoordArray[t][2 * n + 0] = renderUb[t];
+ texCoordArray[t][2 * n + 1] = renderVb[t];
+ }
+ n++;
+ }
+
+ // Adding vertex C.
+ if (recordingShape) {
+ recordedVertices.add(new PVector(c[X], c[Y], c[Z]));
+ recordedColors.add(new float[] { c[R], c[G], c[B], c[A] });
+ recordedNormals.add(new PVector(c[NX], c[NY], c[NZ]));
+ for (int t = 0; t < numTextures; t++) {
+ recordedTexCoords[t].add(new PVector(vertexU[nc][t], vertexV[nc][t], 0.0f));
+ }
+ // Idem to comment in section corresponding to vertex A.
+ for (int t = numTextures; t < maxTextureUnits; t++) {
+ recordedTexCoords[t].add(new PVector(0.0f, 0.0f, 0.0f));
+ }
+ } else {
+ vertexArray[3 * n + 0] = c[X];
+ vertexArray[3 * n + 1] = c[Y];
+ vertexArray[3 * n + 2] = c[Z];
+ colorArray[4 * n + 0] = c[R];
+ colorArray[4 * n + 1] = c[G];
+ colorArray[4 * n + 2] = c[B];
+ colorArray[4 * n + 3] = c[A];
+ normalArray[3 * n + 0] = c[NX];
+ normalArray[3 * n + 1] = c[NY];
+ normalArray[3 * n + 2] = c[NZ];
+ for (int t = 0; t < numTextures; t++) {
+ texCoordArray[t][2 * n + 0] = renderUc[t];
+ texCoordArray[t][2 * n + 1] = renderVc[t];
+ }
+ n++;
+ }
+
+ i++;
+ }
+
+ if (!recordingShape) {
+ vertexBuffer.put(vertexArray);
+ colorBuffer.put(colorArray);
+ normalBuffer.put(normalArray);
+ for (int t = 0; t < numTextures; t++) {
+ texCoordBuffer[t].put(texCoordArray[t]);
+ }
+
+ vertexBuffer.position(0);
+ colorBuffer.position(0);
+ normalBuffer.position(0);
+ for (int t = 0; t < numTextures; t++) {
+ texCoordBuffer[t].position(0);
+ }
+
+ gl2f.glVertexPointer(3, GL.GL_FLOAT, 0, vertexBuffer);
+ gl2f.glColorPointer(4, GL.GL_FLOAT, 0, colorBuffer);
+ gl2f.glNormalPointer(GL.GL_FLOAT, 0, normalBuffer);
+ for (int t = 0; t < numTextures; t++) {
+ gl2f.glClientActiveTexture(GL.GL_TEXTURE0 + t);
+ gl2f.glTexCoordPointer(2, GL.GL_FLOAT, 0, texCoordBuffer[t]);
+ }
+ gl2f.glDrawArrays(GL.GL_TRIANGLES, 0, 3 * faceLength[j]);
+ }
+
+ if (0 < numTextures) {
+ if (1 < numTextures) {
+ clearMultitextureBlend(numTextures);
+ }
+ for (int t = 0; t < numTextures; t++) {
+ PTexture tex = renderTextures[t];
+ gl.glActiveTexture(GL.GL_TEXTURE0 + t);
+ gl.glBindTexture(tex.getGLTarget(), 0);
+ }
+ // Disable the texture targets at the end in a separate loop, otherwise the
+ // textures are not properly unbound. Example: we have multitexturing, with
+ // two 2D textures. If the glDisable() call in in the previous loop, then the
+ // 2D texture target is disabled in the first iteration, which invalidates the
+ // glBindTexture in the second iteration.
+ for (int t = 0; t < numTextures; t++) {
+ PTexture tex = renderTextures[t];
+ gl.glDisable(tex.getGLTarget());
+ }
+
+ gl2f.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+ }
+ }
+
+ gl2f.glDisableClientState(GL2.GL_NORMAL_ARRAY);
+ gl2f.glDisableClientState(GL2.GL_COLOR_ARRAY);
+ gl2f.glDisableClientState(GL2.GL_VERTEX_ARRAY);
+
+ report("render_triangles out");
+ }
+
+
+ // protected void rawTriangles(int start, int stop) // PGraphics3D
+
+ /**
+ * Triangulate the current polygon.
+ *
+ * Simple ear clipping polygon triangulation adapted from code by John W.
+ * Ratcliff (jratcliff at verant.com). Presumably this
+ * bit of code from the web.
+ */
+ protected void addPolygonTriangles() {
+ if (vertexOrder.length != vertices.length) {
+ int[] temp = new int[vertices.length];
+ // since vertex_start may not be zero, might need to keep old stuff around
+ PApplet.arrayCopy(vertexOrder, temp, vertexCount);
+ vertexOrder = temp;
+ }
+
+ // this clipping algorithm only works in 2D, so in cases where a
+ // polygon is drawn perpendicular to the z-axis, the area will be zero,
+ // and triangulation will fail. as such, when the area calculates to
+ // zero, figure out whether x or y is empty, and calculate based on the
+ // two dimensions that actually contain information.
+ // http://dev.processing.org/bugs/show_bug.cgi?id=111
+ int d1 = X;
+ int d2 = Y;
+ // this brings up the nastier point that there may be cases where
+ // a polygon is irregular in space and will throw off the
+ // clockwise/counterclockwise calculation. for instance, if clockwise
+ // relative to x and z, but counter relative to y and z or something
+ // like that.. will wait to see if this is in fact a problem before
+ // hurting my head on the math.
+
+ /*
+ * // trying to track down bug #774 for (int i = vertex_start; i <
+ * vertex_end; i++) { if (i > vertex_start) { if (vertices[i-1][MX] ==
+ * vertices[i][MX] && vertices[i-1][MY] == vertices[i][MY]) {
+ * System.out.print("**** " ); } } System.out.println(i + " " +
+ * vertices[i][MX] + " " + vertices[i][MY]); } System.out.println();
+ */
+
+ // first we check if the polygon goes clockwise or counterclockwise
+ float area = 0;
+ for (int p = shapeLast - 1, q = shapeFirst; q < shapeLast; p = q++) {
+ area += (vertices[q][d1] * vertices[p][d2] - vertices[p][d1]
+ * vertices[q][d2]);
+ }
+ // rather than checking for the perpendicular case first, only do it
+ // when the area calculates to zero. checking for perpendicular would be
+ // a needless waste of time for the 99% case.
+ if (area == 0) {
+ // figure out which dimension is the perpendicular axis
+ boolean foundValidX = false;
+ boolean foundValidY = false;
+
+ for (int i = shapeFirst; i < shapeLast; i++) {
+ for (int j = i; j < shapeLast; j++) {
+ if (vertices[i][X] != vertices[j][X])
+ foundValidX = true;
+ if (vertices[i][Y] != vertices[j][Y])
+ foundValidY = true;
+ }
+ }
+
+ if (foundValidX) {
+ // d1 = MX; // already the case
+ d2 = Z;
+ } else if (foundValidY) {
+ // ermm.. which is the proper order for cw/ccw here?
+ d1 = Y;
+ d2 = Z;
+ } else {
+ // screw it, this polygon is just f-ed up
+ return;
+ }
+
+ // re-calculate the area, with what should be good values
+ for (int p = shapeLast - 1, q = shapeFirst; q < shapeLast; p = q++) {
+ area += (vertices[q][d1] * vertices[p][d2] - vertices[p][d1]
+ * vertices[q][d2]);
+ }
+ }
+
+ // don't allow polygons to come back and meet themselves,
+ // otherwise it will anger the triangulator
+ // http://dev.processing.org/bugs/show_bug.cgi?id=97
+ float vfirst[] = vertices[shapeFirst];
+ float vlast[] = vertices[shapeLast - 1];
+ if ((PApplet.abs(vfirst[X] - vlast[X]) < EPSILON)
+ && (PApplet.abs(vfirst[Y] - vlast[Y]) < EPSILON)
+ && (PApplet.abs(vfirst[Z] - vlast[Z]) < EPSILON)) {
+ shapeLast--;
+ }
+
+ // then sort the vertices so they are always in a counterclockwise order
+ int j = 0;
+ if (area > 0) {
+ for (int i = shapeFirst; i < shapeLast; i++) {
+ j = i - shapeFirst;
+ vertexOrder[j] = i;
+ }
+ } else {
+ for (int i = shapeFirst; i < shapeLast; i++) {
+ j = i - shapeFirst;
+ vertexOrder[j] = (shapeLast - 1) - j;
+ }
+ }
+
+ // remove vc-2 Vertices, creating 1 triangle every time
+ int vc = shapeLast - shapeFirst;
+ int count = 2 * vc; // complex polygon detection
+
+ for (int m = 0, v = vc - 1; vc > 2;) {
+ boolean snip = true;
+
+ // if we start over again, is a complex polygon
+ if (0 >= (count--)) {
+ break; // triangulation failed
+ }
+
+ // get 3 consecutive vertices
+ int u = v;
+ if (vc <= u)
+ u = 0; // previous
+ v = u + 1;
+ if (vc <= v)
+ v = 0; // current
+ int w = v + 1;
+ if (vc <= w)
+ w = 0; // next
+
+ // Upgrade values to doubles, and multiply by 10 so that we can have
+ // some better accuracy as we tessellate. This seems to have negligible
+ // speed differences on Windows and Intel Macs, but causes a 50% speed
+ // drop for PPC Macs with the bug's example code that draws ~200 points
+ // in a concave polygon. Apple has abandoned PPC so we may as well too.
+ // http://dev.processing.org/bugs/show_bug.cgi?id=774
+
+ // triangle A B C
+ double Ax = -10 * vertices[vertexOrder[u]][d1];
+ double Ay = 10 * vertices[vertexOrder[u]][d2];
+ double Bx = -10 * vertices[vertexOrder[v]][d1];
+ double By = 10 * vertices[vertexOrder[v]][d2];
+ double Cx = -10 * vertices[vertexOrder[w]][d1];
+ double Cy = 10 * vertices[vertexOrder[w]][d2];
+
+ // first we check if continues going ccw
+ if (EPSILON > (((Bx - Ax) * (Cy - Ay)) - ((By - Ay) * (Cx - Ax)))) {
+ continue;
+ }
+
+ for (int p = 0; p < vc; p++) {
+ if ((p == u) || (p == v) || (p == w)) {
+ continue;
+ }
+
+ double Px = -10 * vertices[vertexOrder[p]][d1];
+ double Py = 10 * vertices[vertexOrder[p]][d2];
+
+ double ax = Cx - Bx;
+ double ay = Cy - By;
+ double bx = Ax - Cx;
+ double by = Ay - Cy;
+ double cx = Bx - Ax;
+ double cy = By - Ay;
+ double apx = Px - Ax;
+ double apy = Py - Ay;
+ double bpx = Px - Bx;
+ double bpy = Py - By;
+ double cpx = Px - Cx;
+ double cpy = Py - Cy;
+
+ double aCROSSbp = ax * bpy - ay * bpx;
+ double cCROSSap = cx * apy - cy * apx;
+ double bCROSScp = bx * cpy - by * cpx;
+
+ if ((aCROSSbp >= 0.0) && (bCROSScp >= 0.0) && (cCROSSap >= 0.0)) {
+ snip = false;
+ }
+ }
+
+ if (snip) {
+ addTriangle(vertexOrder[u], vertexOrder[v], vertexOrder[w]);
+
+ m++;
+
+ // remove v from remaining polygon
+ for (int s = v, t = v + 1; t < vc; s++, t++) {
+ vertexOrder[s] = vertexOrder[t];
+ }
+ vc--;
+
+ // reset error detection counter
+ count = 2 * vc;
+ }
+ }
+ }
+
+ protected void expandBuffers() {
+ int newSize = vertexBuffer.capacity() / 3 << 1;
+
+ ByteBuffer vbb = ByteBuffer.allocateDirect(newSize * 3 * SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ vertexBuffer = vbb.asFloatBuffer();
+
+ ByteBuffer cbb = ByteBuffer.allocateDirect(newSize * 4 * SIZEOF_FLOAT);
+ cbb.order(ByteOrder.nativeOrder());
+ colorBuffer = cbb.asFloatBuffer();
+
+ ByteBuffer nbb = ByteBuffer.allocateDirect(newSize * 3 * SIZEOF_FLOAT);
+ nbb.order(ByteOrder.nativeOrder());
+ normalBuffer = nbb.asFloatBuffer();
+
+ for (int t = 0; t < numTexBuffers; t++) {
+ ByteBuffer tbb = ByteBuffer.allocateDirect(newSize * 2 * SIZEOF_FLOAT);
+ tbb.order(ByteOrder.nativeOrder());
+ texCoordBuffer[t] = tbb.asFloatBuffer();
+ }
+
+ vertexArray = new float[newSize * 3];
+ colorArray = new float[newSize * 4];
+ normalArray = new float[newSize * 3];
+ for (int t = 0; t < numTexBuffers; t++) {
+ texCoordArray[t] = new float[newSize * 2];
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // RENDERING
+
+ // public void flush()
+
+ // protected void render()
+
+ // protected void sort()
+
+ //////////////////////////////////////////////////////////////
+
+ // POINT, LINE, TRIANGLE, QUAD
+
+ // Because vertex(x, y) is mapped to vertex(x, y, 0), none of these commands
+ // need to be overridden from their default implementation in PGraphics.
+
+ // public void point(float x, float y)
+
+ // public void point(float x, float y, float z)
+
+ // public void line(float x1, float y1, float x2, float y2)
+
+ // public void line(float x1, float y1, float z1,
+ // float x2, float y2, float z2)
+
+ // public void triangle(float x1, float y1, float x2, float y2,
+ // float x3, float y3)
+
+ // public void quad(float x1, float y1, float x2, float y2,
+ // float x3, float y3, float x4, float y4)
+
+ //////////////////////////////////////////////////////////////
+
+ // RECT
+
+ // public void rectMode(int mode)
+
+ // public void rect(float a, float b, float c, float d)
+
+ // protected void rectImpl(float x1, float y1, float x2, float y2)
+
+ //////////////////////////////////////////////////////////////
+
+ // ELLIPSE
+
+ // public void ellipseMode(int mode)
+
+ protected void ellipseImpl(float x, float y, float w, float h) {
+ float radiusH = w / 2;
+ float radiusV = h / 2;
+
+ float centerX = x + radiusH;
+ float centerY = y + radiusV;
+
+ float sx1 = screenX(x, y);
+ float sy1 = screenY(x, y);
+ float sx2 = screenX(x + w, y + h);
+ float sy2 = screenY(x + w, y + h);
+
+ if (fill) {
+ int accuracy = (int) (TWO_PI * PApplet.dist(sx1, sy1, sx2, sy2) / 20);
+ if (accuracy < 6)
+ accuracy = 6;
+
+ float inc = (float) SINCOS_LENGTH / accuracy;
+ float val = 0;
+
+ boolean strokeSaved = stroke;
+ stroke = false;
+ boolean smoothSaved = smooth;
+ if (smooth && stroke) {
+ smooth = false;
+ }
+
+ beginShape(TRIANGLE_FAN);
+ normal(0, 0, 1);
+ vertex(centerX, centerY);
+ for (int i = 0; i < accuracy; i++) {
+ vertex(centerX + cosLUT[(int) val] * radiusH, centerY
+ + sinLUT[(int) val] * radiusV);
+ val = (val + inc) % SINCOS_LENGTH;
+ }
+ // back to the beginning
+ vertex(centerX + cosLUT[0] * radiusH, centerY + sinLUT[0] * radiusV);
+ endShape();
+
+ stroke = strokeSaved;
+ smooth = smoothSaved;
+ }
+
+ if (stroke) {
+ int accuracy = (int) (TWO_PI * PApplet.dist(sx1, sy1, sx2, sy2) / 8);
+ if (accuracy < 6)
+ accuracy = 6;
+
+ float inc = (float) SINCOS_LENGTH / accuracy;
+ float val = 0;
+
+ boolean savedFill = fill;
+ fill = false;
+
+ val = 0;
+ beginShape();
+ for (int i = 0; i < accuracy; i++) {
+ vertex(centerX + cosLUT[(int) val] * radiusH, centerY
+ + sinLUT[(int) val] * radiusV);
+ val = (val + inc) % SINCOS_LENGTH;
+ }
+ endShape(CLOSE);
+
+ fill = savedFill;
+ }
+ }
+
+ // public void ellipse(float a, float b, float c, float d)
+
+ // public void arc(float a, float b, float c, float d,
+ // float start, float stop)
+
+ // protected void arcImpl(float x, float y, float w, float h,
+ // float start, float stop)
+
+ //////////////////////////////////////////////////////////////
+
+ // BOX
+
+ // TODO GL and GLUT in GL ES doesn't offer functions to create
+ // cubes.
+
+ // public void box(float size)
+
+ // public void box(float w, float h, float d) // P3D
+
+ //////////////////////////////////////////////////////////////
+
+ // SPHERE
+
+ // TODO GL and GLUT in GL ES doesn't offer functions to create
+ // spheres.
+
+ // public void sphereDetail(int res)
+
+ // public void sphereDetail(int ures, int vres)
+
+ // public void sphere(float r)
+
+ //////////////////////////////////////////////////////////////
+
+ // BEZIER
+
+ // public float bezierPoint(float a, float b, float c, float d, float t)
+
+ // public float bezierTangent(float a, float b, float c, float d, float t)
+
+ // public void bezierDetail(int detail)
+
+ // public void bezier(float x1, float y1,
+ // float x2, float y2,
+ // float x3, float y3,
+ // float x4, float y4)
+
+ // public void bezier(float x1, float y1, float z1,
+ // float x2, float y2, float z2,
+ // float x3, float y3, float z3,
+ // float x4, float y4, float z4)
+
+ //////////////////////////////////////////////////////////////
+
+ // CATMULL-ROM CURVES
+
+ // public float curvePoint(float a, float b, float c, float d, float t)
+
+ // public float curveTangent(float a, float b, float c, float d, float t)
+
+ // public void curveDetail(int detail)
+
+ // public void curveTightness(float tightness)
+
+ // public void curve(float x1, float y1,
+ // float x2, float y2,
+ // float x3, float y3,
+ // float x4, float y4)
+
+ // public void curve(float x1, float y1, float z1,
+ // float x2, float y2, float z2,
+ // float x3, float y3, float z3,
+ // float x4, float y4, float z4)
+
+ //////////////////////////////////////////////////////////////
+
+ // IMAGES
+
+ // public void imageMode(int mode)
+
+ // public void image(PImage image, float x, float y)
+
+ // public void image(PImage image, float x, float y, float c, float d)
+
+ // public void image(PImage image,
+ // float a, float b, float c, float d,
+ // int u1, int v1, int u2, int v2)
+
+ // protected void imageImpl(PImage image,
+ // float x1, float y1, float x2, float y2,
+ // int u1, int v1, int u2, int v2)
+
+ //////////////////////////////////////////////////////////////
+
+ // SHAPE
+
+ // public void shapeMode(int mode)
+
+ public void shape(PShape3D shape) {
+ shape.draw(this);
+ }
+
+ public void shape(PShape3D shape, float x, float y) {
+ shape(shape, x, y, 0);
+ }
+
+ public void shape(PShape3D shape, float x, float y, float z) {
+ pushMatrix();
+ translate(x, y, z);
+ shape.draw(this);
+ popMatrix();
+ }
+
+ // public void shape(PShape shape, float x, float y, float c, float d)
+
+ //////////////////////////////////////////////////////////////
+
+ // TEXT SETTINGS
+
+ // public void textAlign(int align)
+
+ // public void textAlign(int alignX, int alignY)
+
+ // public float textAscent()
+
+ // public float textDescent()
+
+ // public void textFont(PFont which)
+
+ // public void textFont(PFont which, float size)
+
+ // public void textLeading(float leading)
+
+ // public void textMode(int mode)
+
+ // protected boolean textModeCheck(int mode)
+
+ // public void textSize(float size)
+
+ // public float textWidth(char c)
+
+ // public float textWidth(String str)
+
+ // protected float textWidthImpl(char buffer[], int start, int stop)
+
+ //////////////////////////////////////////////////////////////
+
+ // TEXT BLOCK
+
+ public void beginText() {
+ if (textMode == MODEL) {
+ textBlockMode = true;
+ textVertexCount = 0;
+ }
+ }
+
+
+ public void endText() {
+ if (textBlockMode) {
+ textBlockMode = false;
+
+ if (0 < textVertexCount) {
+ // Now we render all the text that has been pushed between
+ // beginText/endText.
+
+ if (!blend || blendMode != BLEND) {
+ gl.glEnable(GL.GL_BLEND);
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ textTex.setTexture(textBlockTex);
+ renderTextModel();
+
+ // Restoring current blend mode.
+ if (blend) {
+ blend(blendMode);
+ } else {
+ noBlend();
+ }
+
+ gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
+ gl.glDisable(GL.GL_TEXTURE_2D);
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // TEXT IMPL
+
+ // protected void textLineAlignImpl(char buffer[], int start, int stop,
+ // float x, float y)
+
+ /**
+ * Implementation of actual drawing for a line of text.
+ */
+ protected void textLineImpl(char buffer[], int start, int stop, float x, float y) {
+ // Init opengl state for text rendering...
+ gl.glEnable(GL.GL_TEXTURE_2D);
+
+ if (!blend || blendMode != BLEND) {
+ gl.glEnable(GL.GL_BLEND);
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ }
+
+ textTex = (PFontTexture)textFont.getCache(pgl);
+ if (textTex == null) {
+ textTex = new PFontTexture(parent, textFont, maxTextureSize, maxTextureSize);
+ textFont.setCache(this, textTex);
+ textTex.addAllGlyphsToTexture();
+ }
+ textTex.setFirstTexture();
+
+ // Setting the current fill color as the font color.
+ setFillColor();
+
+ if (textMode == MODEL) {
+ if (textVertexBuffer == null) {
+ allocateTextModel();
+ }
+
+ // Setting Z axis as the normal to the text geometry
+ setDefNormals(0, 0, 1);
+
+ if (!textBlockMode) {
+ // Resetting vertex count when we are not defining a
+ // block of text.
+ textVertexCount = 0;
+ }
+ }
+
+ super.textLineImpl(buffer, start, stop, x, y);
+
+ if (textMode == MODEL && 0 < textVertexCount) {
+ if (!textBlockMode) {
+ // Pushing text geometry to the GPU.
+ renderTextModel();
+ } else {
+ // We don't push any geometry here because we will
+ // do it when endText is called. For now we just
+ // save the current texture.
+ textBlockTex = textTex.currentTex;
+ }
+ }
+
+ // Restoring current blend mode.
+ if (blend) {
+ blend(blendMode);
+ } else {
+ noBlend();
+ }
+
+ gl.glBindTexture(GL.GL_TEXTURE_2D, 0);
+ gl.glDisable(GL.GL_TEXTURE_2D);
+ }
+
+ protected void textCharImpl(char ch, float x, float y) {
+ PFont.Glyph glyph = textFont.getGlyph(ch);
+
+ if (glyph != null) {
+ PFontTexture.TextureInfo tinfo = textTex.getTexInfo(glyph);
+
+ if (tinfo == null) {
+ // Adding new glyph to the font texture.
+ tinfo = textTex.addToTexture(glyph);
+ }
+
+ if (textMode == MODEL) {
+ float high = glyph.height / (float) textFont.getSize();
+ float bwidth = glyph.width / (float) textFont.getSize();
+ float lextent = glyph.leftExtent / (float) textFont.getSize();
+ float textent = glyph.topExtent / (float) textFont.getSize();
+
+ float x1 = x + lextent * textSize;
+ float y1 = y - textent * textSize;
+ float x2 = x1 + bwidth * textSize;
+ float y2 = y1 + high * textSize;
+
+ textCharModelImpl(tinfo, x1, y1, x2, y2);
+
+ } else if (textMode == SCREEN) {
+ int xx = (int) x + glyph.leftExtent;
+ int yy = (int) y - glyph.topExtent;
+
+ int w0 = glyph.width;
+ int h0 = glyph.height;
+
+ textCharScreenImpl(tinfo, xx, yy, w0, h0);
+ }
+ }
+ }
+
+ protected void textCharModelImpl(PFontTexture.TextureInfo info, float x1, float y1,
+ float x2, float y2) {
+ if ((textTex.currentTex != info.texIndex) ||
+ (textBlockMode && textBlockTex != info.texIndex)) {
+ if (0 < textVertexCount) {
+ // Current texture changes (the font is so large that needs more than one texture).
+ // So rendering all we got until now, and reseting vertex counter.
+ renderTextModel();
+ textVertexCount = 0;
+ }
+ textTex.setTexture(info.texIndex);
+ }
+
+ // Division by three needed because each int element in the buffer is used
+ // to store three coordinates.
+ if (textVertexBuffer.capacity() / 3 < textVertexCount + 6) {
+ expandTextBuffers();
+ }
+
+ int n = textVertexCount;
+ textVertexArray[3 * n + 0] = x1;
+ textVertexArray[3 * n + 1] = y1;
+ textVertexArray[3 * n + 2] = 0;
+ textTexCoordArray[2 * n + 0] = info.u0;
+ textTexCoordArray[2 * n + 1] = info.v0;
+ n++;
+
+ textVertexArray[3 * n + 0] = x2;
+ textVertexArray[3 * n + 1] = y2;
+ textVertexArray[3 * n + 2] = 0;
+ textTexCoordArray[2 * n + 0] = info.u1;
+ textTexCoordArray[2 * n + 1] = info.v1;
+ n++;
+
+ textVertexArray[3 * n + 0] = x1;
+ textVertexArray[3 * n + 1] = y2;
+ textVertexArray[3 * n + 2] = 0;
+ textTexCoordArray[2 * n + 0] = info.u0;
+ textTexCoordArray[2 * n + 1] = info.v1;
+ n++;
+
+ textVertexArray[3 * n + 0] = x1;
+ textVertexArray[3 * n + 1] = y1;
+ textVertexArray[3 * n + 2] = 0;
+ textTexCoordArray[2 * n + 0] = info.u0;
+ textTexCoordArray[2 * n + 1] = info.v0;
+ n++;
+
+ textVertexArray[3 * n + 0] = x2;
+ textVertexArray[3 * n + 1] = y1;
+ textVertexArray[3 * n + 2] = 0;
+ textTexCoordArray[2 * n + 0] = info.u1;
+ textTexCoordArray[2 * n + 1] = info.v0;
+ n++;
+
+ textVertexArray[3 * n + 0] = x2;
+ textVertexArray[3 * n + 1] = y2;
+ textVertexArray[3 * n + 2] = 0;
+ textTexCoordArray[2 * n + 0] = info.u1;
+ textTexCoordArray[2 * n + 1] = info.v1;
+ n++;
+
+ textVertexCount = n;
+ }
+
+ protected void textCharScreenImpl(PFontTexture.TextureInfo info, int xx, int yy,
+ int w0, int h0) {
+ if (textTex.currentTex != info.texIndex) {
+ textTex.setTexture(info.texIndex);
+ }
+
+ // There is no need to setup orthographic projection or any related matrix set/restore
+ // operations here because glDrawTexiOES operates on window coordinates:
+ // "glDrawTexiOES takes window coordinates and bypasses the transform pipeline
+ // (except for mapping Z to the depth range), so there is no need for any
+ // matrix setup/restore code."
+ // (from https://www.khronos.org/message_boards/viewtopic.php?f=4&t=948&p=2553).
+ //gl11.glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, info.crop, 0);
+ //gl11x.glDrawTexiOES(xx, height - yy, 0, w0, h0);
+ }
+
+ protected void allocateTextModel() {
+ ByteBuffer vbb = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE * 3 * SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ textVertexBuffer = vbb.asFloatBuffer();
+
+ ByteBuffer tbb = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE * 2 * SIZEOF_FLOAT);
+ tbb.order(ByteOrder.nativeOrder());
+ textTexCoordBuffer = tbb.asFloatBuffer();
+
+ textVertexArray = new float[DEFAULT_BUFFER_SIZE * 3];
+ textTexCoordArray = new float[DEFAULT_BUFFER_SIZE * 2];
+ }
+
+ protected void renderTextModel() {
+ textVertexBuffer.position(0);
+ textTexCoordBuffer.position(0);
+
+ textVertexBuffer.put(textVertexArray);
+ textTexCoordBuffer.put(textTexCoordArray);
+
+ gl2f.glEnableClientState(GL2.GL_VERTEX_ARRAY);
+ gl2f.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+
+ textVertexBuffer.position(0);
+ textTexCoordBuffer.position(0);
+
+ gl2f.glVertexPointer(3, GL.GL_FLOAT, 0, textVertexBuffer);
+ gl2f.glTexCoordPointer(2, GL.GL_FLOAT, 0, textTexCoordBuffer);
+ gl2f.glDrawArrays(GL.GL_TRIANGLES, 0, textVertexCount);
+
+ gl2f.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+ gl2f.glDisableClientState(GL2.GL_VERTEX_ARRAY);
+ }
+
+ protected void expandTextBuffers() {
+ int newSize = textVertexBuffer.capacity() / 3 << 1;
+
+ ByteBuffer vbb = ByteBuffer.allocateDirect(newSize * 3 * SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ textVertexBuffer = vbb.asFloatBuffer();
+
+ ByteBuffer tbb = ByteBuffer.allocateDirect(newSize * 2 * SIZEOF_FLOAT);
+ tbb.order(ByteOrder.nativeOrder());
+ textTexCoordBuffer = tbb.asFloatBuffer();
+
+ textVertexArray = new float[newSize * 3];
+ textTexCoordArray = new float[newSize * 2];
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX STACK
+
+ public void pushMatrix() {
+ gl2f.glPushMatrix();
+ if (usingGLMatrixStack) {
+ if (projectionMode) {
+ projectionStack.push();
+ } else {
+ modelviewStack.push();
+ }
+ }
+ }
+
+
+ public void popMatrix() {
+ gl2f.glPopMatrix();
+ if (usingGLMatrixStack) {
+ if (projectionMode) {
+ projectionStack.pop();
+ projectionUpdated = false;
+ } else {
+ modelviewStack.pop();
+ modelviewUpdated = false;
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX TRANSFORMATIONS
+
+ public void translate(float tx, float ty) {
+ translate(tx, ty, 0);
+ }
+
+ public void translate(float tx, float ty, float tz) {
+ // Translation along Y is inverted to account for Processing's inverted Y
+ // axis
+ // with respect to OpenGL. The other place where inversion occurs is when
+ // drawing the geometric primitives (vertex arrays), where a -1 scaling
+ // along Y is applied.
+ gl2f.glTranslatef(tx, ty, tz);
+ if (usingGLMatrixStack) {
+ if (projectionMode) {
+ projectionStack.translate(tx, ty, tz);
+ projectionUpdated = false;
+ } else {
+ modelviewStack.translate(tx, ty, tz);
+ modelviewUpdated = false;
+ }
+ }
+ }
+
+ /**
+ * Two dimensional rotation. Same as rotateZ (this is identical to a 3D
+ * rotation along the z-axis) but included for clarity -- it'd be weird for
+ * people drawing 2D graphics to be using rotateZ. And they might kick our a--
+ * for the confusion.
+ */
+ public void rotate(float angle) {
+ rotateZ(angle);
+ }
+
+ public void rotateX(float angle) {
+ rotate(angle, 1, 0, 0);
+ }
+
+ public void rotateY(float angle) {
+ rotate(angle, 0, 1, 0);
+ }
+
+ public void rotateZ(float angle) {
+ rotate(angle, 0, 0, 1);
+ }
+
+ /**
+ * Rotate around an arbitrary vector, similar to glRotate(), except that it
+ * takes radians (instead of degrees).
+ */
+ public void rotate(float angle, float v0, float v1, float v2) {
+ gl2f.glRotatef(PApplet.degrees(angle), v0, v1, v2);
+ if (usingGLMatrixStack) {
+ if (projectionMode) {
+ projectionStack.rotate(angle, v0, v1, v2);
+ projectionUpdated = false;
+ } else {
+ modelviewStack.rotate(angle, v0, v1, v2);
+ modelviewUpdated = false;
+ }
+ }
+ }
+
+ /**
+ * Same as scale(s, s, s).
+ */
+ public void scale(float s) {
+ scale(s, s, s);
+ }
+
+ /**
+ * Same as scale(sx, sy, 1).
+ */
+ public void scale(float sx, float sy) {
+ scale(sx, sy, 1);
+ }
+
+ /**
+ * Scale in three dimensions.
+ */
+ public void scale(float x, float y, float z) {
+ if (manipulatingCamera) {
+ scalingDuringCamManip = true;
+ }
+ gl2f.glScalef(x, y, z);
+ if (usingGLMatrixStack) {
+ if (projectionMode) {
+ projectionStack.scale(x, y, z);
+ projectionUpdated = false;
+ } else {
+ modelviewStack.scale(x, y, z);
+ modelviewUpdated = false;
+ }
+ }
+ }
+
+ public void shearX(float angle) {
+ float t = (float) Math.tan(angle);
+ applyMatrix(1, t, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+ }
+
+ public void shearY(float angle) {
+ float t = (float) Math.tan(angle);
+ applyMatrix(1, 0, 0, 0, t, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX MORE!
+
+ public void resetMatrix() {
+ gl2f.glLoadIdentity();
+ }
+
+ public void applyMatrix(PMatrix2D source) {
+ applyMatrix(source.m00, source.m01, source.m02, source.m10, source.m11,
+ source.m12);
+ }
+
+ public void applyMatrix(float n00, float n01, float n02, float n10,
+ float n11, float n12) {
+ applyMatrix(n00, n01, n02, 0, n10, n11, n12, 0, 0, 0, 1, 0, 0, 0, 0, 1);
+ }
+
+ public void applyMatrix(PMatrix3D source) {
+ applyMatrix(source.m00, source.m01, source.m02, source.m03, source.m10,
+ source.m11, source.m12, source.m13, source.m20, source.m21, source.m22,
+ source.m23, source.m30, source.m31, source.m32, source.m33);
+ }
+
+ /**
+ * Apply a 4x4 transformation matrix to the modelview stack using
+ * glMultMatrix(). This call will be slow because it will try to calculate the
+ * inverse of the transform. So avoid it whenever possible.
+ */
+ public void applyMatrix(float n00, float n01, float n02, float n03,
+ float n10, float n11, float n12, float n13, float n20, float n21,
+ float n22, float n23, float n30, float n31, float n32, float n33) {
+
+ gltemp[0] = n00;
+ gltemp[1] = n10;
+ gltemp[2] = n20;
+ gltemp[3] = n30;
+
+ gltemp[4] = n01;
+ gltemp[5] = n11;
+ gltemp[6] = n21;
+ gltemp[7] = n31;
+
+ gltemp[8] = n02;
+ gltemp[9] = n12;
+ gltemp[10] = n22;
+ gltemp[11] = n32;
+
+ gltemp[12] = n03;
+ gltemp[13] = n13;
+ gltemp[14] = n23;
+ gltemp[15] = n33;
+
+ gl2f.glMultMatrixf(gltemp, 0);
+
+ if (usingGLMatrixStack) {
+ if (projectionMode) {
+ projectionStack.mult(gltemp);
+ projectionUpdated = false;
+ } else {
+ modelviewStack.mult(gltemp);
+ modelviewUpdated = false;
+ }
+ }
+ }
+
+ public void updateModelview() {
+ updateModelview(true);
+ }
+
+ public void updateModelview(boolean calcInv) {
+ copyPMatrixToGLArray(modelview, glmodelview);
+ if (calcInv) {
+ calculateModelviewInverse();
+ } else {
+ copyPMatrixToGLArray(modelviewInv, glmodelviewInv);
+ }
+ gl2f.glLoadMatrixf(glmodelview, 0);
+ if (usingGLMatrixStack) {
+ modelviewStack.set(glmodelview);
+ }
+ modelviewUpdated = true;
+ }
+
+ public void updateCamera() {
+ if (!manipulatingCamera) {
+ throw new RuntimeException("Cannot call updateCamera() "
+ + "without first calling beginCamera()");
+ }
+ copyPMatrixToGLArray(camera, glmodelview);
+ gl2f.glLoadMatrixf(glmodelview, 0);
+ if (usingGLMatrixStack) {
+ modelviewStack.set(glmodelview);
+ }
+ scalingDuringCamManip = true; // Assuming general transformation.
+ modelviewUpdated = true;
+ }
+
+ // This method is needed to copy a PMatrix3D into a opengl array.
+ // The PMatrix3D.get(float[]) is not useful, because PMatrix3D assumes
+ // row-major ordering of the elements of the float array, and opengl
+ // uses column-major ordering.
+ protected void copyPMatrixToGLArray(PMatrix3D src, float[] dest) {
+ dest[0] = src.m00;
+ dest[1] = src.m10;
+ dest[2] = src.m20;
+ dest[3] = src.m30;
+
+ dest[4] = src.m01;
+ dest[5] = src.m11;
+ dest[6] = src.m21;
+ dest[7] = src.m31;
+
+ dest[8] = src.m02;
+ dest[9] = src.m12;
+ dest[10] = src.m22;
+ dest[11] = src.m32;
+
+ dest[12] = src.m03;
+ dest[13] = src.m13;
+ dest[14] = src.m23;
+ dest[15] = src.m33;
+ }
+
+ // This method is needed to copy an opengl array into a PMatrix3D.
+ // The PMatrix3D.set(float[]) is not useful, because PMatrix3D assumes
+ // row-major ordering of the elements of the float array, and opengl
+ // uses column-major ordering.
+ protected void copyGLArrayToPMatrix(float[] src, PMatrix3D dest) {
+ dest.m00 = src[0];
+ dest.m10 = src[1];
+ dest.m20 = src[2];
+ dest.m30 = src[3];
+
+ dest.m01 = src[4];
+ dest.m11 = src[5];
+ dest.m21 = src[6];
+ dest.m31 = src[7];
+
+ dest.m02 = src[8];
+ dest.m12 = src[9];
+ dest.m22 = src[10];
+ dest.m32 = src[11];
+
+ dest.m03 = src[12];
+ dest.m13 = src[13];
+ dest.m23 = src[14];
+ dest.m33 = src[15];
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // MATRIX GET/SET/PRINT
+
+ public PMatrix getMatrix() {
+ PMatrix res = new PMatrix3D();
+ copyGLArrayToPMatrix(glmodelview, (PMatrix3D)res);
+ return res;
+ }
+
+ // public PMatrix2D getMatrix(PMatrix2D target)
+
+ public PMatrix3D getMatrix(PMatrix3D target) {
+ if (target == null) {
+ target = new PMatrix3D();
+ }
+ copyGLArrayToPMatrix(glmodelview, target);
+ return target;
+ }
+
+ // public void setMatrix(PMatrix source)
+
+ public void setMatrix(PMatrix2D source) {
+ // not efficient, but at least handles the inverse stuff.
+ resetMatrix();
+ applyMatrix(source);
+ }
+
+ /**
+ * Set the current transformation to the contents of the specified source.
+ */
+ public void setMatrix(PMatrix3D source) {
+ // not efficient, but at least handles the inverse stuff.
+ resetMatrix();
+ applyMatrix(source);
+ }
+
+ /**
+ * Print the current model (or "transformation") matrix.
+ */
+ public void printMatrix() {
+ PMatrix3D temp = new PMatrix3D();
+ copyGLArrayToPMatrix(glmodelview, temp);
+ temp.print();
+ }
+
+ /*
+ * This function checks if the modelview matrix is set up to likely be drawing
+ * in 2D. It merely checks if the non-translational piece of the matrix is
+ * unity. If this is to be used, it should be coupled with a check that the
+ * raw vertex coordinates lie in the z=0 plane. Mainly useful for applying
+ * sub-pixel shifts to avoid 2d artifacts in the screen plane. Added by
+ * ewjordan 6/13/07
+ *
+ * TODO need to invert the logic here so that we can simply return the value,
+ * rather than calculating true/false and returning it.
+ */
+ /*
+ * private boolean drawing2D() { if (modelview.m00 != 1.0f || modelview.m11 !=
+ * 1.0f || modelview.m22 != 1.0f || // check scale modelview.m01 != 0.0f ||
+ * modelview.m02 != 0.0f || // check rotational pieces modelview.m10 != 0.0f
+ * || modelview.m12 != 0.0f || modelview.m20 != 0.0f || modelview.m21 != 0.0f
+ * || !((camera.m23-modelview.m23) <= EPSILON && (camera.m23-modelview.m23) >=
+ * -EPSILON)) { // check for z-translation // Something about the modelview
+ * matrix indicates 3d drawing // (or rotated 2d, in which case 2d subpixel
+ * fixes probably aren't needed) return false; } else { //The matrix is
+ * mapping z=0 vertices to the screen plane, // which means it's likely that
+ * 2D drawing is happening. return true; } }
+ */
+
+ //////////////////////////////////////////////////////////////
+
+ // PROJECTION
+
+ public void beginProjection() {
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ projectionMode = true;
+ }
+
+ public void endProjection() {
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ projectionMode = false;
+ }
+
+ protected void getProjectionMatrix() {
+ if (usingGLMatrixStack) {
+ projectionStack.get(glprojection);
+ } else {
+ gl2f.glGetFloatv(GL2.GL_PROJECTION_MATRIX, glprojection, 0);
+ }
+ copyGLArrayToPMatrix(glprojection, projection);
+ projectionUpdated = true;
+ }
+
+ public void updateProjection() {
+ copyPMatrixToGLArray(projection, glprojection);
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ gl2f.glLoadMatrixf(glprojection, 0);
+ if (!projectionMode) {
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ }
+ if (usingGLMatrixStack) {
+ projection.set(glmodelview);
+ }
+ projectionUpdated = true;
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // CAMERA
+
+ /**
+ * Set matrix mode to the camera matrix (instead of the current transformation
+ * matrix). This means applyMatrix, resetMatrix, etc. will affect the camera.
+ *
+ * Note that the camera matrix is *not* the perspective matrix, it contains
+ * the values of the modelview matrix immediatly after the latter was
+ * initialized with ortho() or camera(), or the modelview matrix as result of
+ * the operations applied between beginCamera()/endCamera().
+ *
+ * beginCamera() specifies that all coordinate transforms until endCamera()
+ * should be pre-applied in inverse to the camera transform matrix. Note that
+ * this is only challenging when a user specifies an arbitrary matrix with
+ * applyMatrix(). Then that matrix will need to be inverted, which may not be
+ * possible. But take heart, if a user is applying a non-invertible matrix to
+ * the camera transform, then he is clearly up to no good, and we can wash our
+ * hands of those bad intentions.
+ *
+ * begin/endCamera clauses do not automatically reset the camera transform
+ * matrix. That's because we set up a nice default camera transform int
+ * setup(), and we expect it to hold through draw(). So we don't reset the
+ * camera transform matrix at the top of draw(). That means that an
+ * innocuous-looking clause like
+ *
+ *
+ * beginCamera();
+ * translate(0, 0, 10);
+ * endCamera();
+ *
+ *
+ * at the top of draw(), will result in a runaway camera that shoots
+ * infinitely out of the screen over time. In order to prevent this, it is
+ * necessary to call some function that does a hard reset of the camera
+ * transform matrix inside of begin/endCamera. Two options are
+ *
+ *
+ * camera(); // sets up the nice default camera transform
+ * resetMatrix(); // sets up the identity camera transform
+ *
+ *
+ * So to rotate a camera a constant amount, you might try
+ *
+ *
+ * beginCamera();
+ * camera();
+ * rotateY(PI / 8);
+ * endCamera();
+ *
+ */
+ public void beginCamera() {
+ if (manipulatingCamera) {
+ throw new RuntimeException("beginCamera() cannot be called again "
+ + "before endCamera()");
+ } else {
+ manipulatingCamera = true;
+ scalingDuringCamManip = false;
+ }
+ }
+
+ /**
+ * Record the current settings into the camera matrix, and set the matrix mode
+ * back to the current transformation matrix.
+ *
+ * Note that this will destroy any settings to scale(), translate(), or
+ * whatever, because the final camera matrix will be copied (not multiplied)
+ * into the modelview.
+ */
+ public void endCamera() {
+ if (!manipulatingCamera) {
+ throw new RuntimeException("Cannot call endCamera() "
+ + "without first calling beginCamera()");
+ }
+
+ getModelviewMatrix();
+
+ if (scalingDuringCamManip) {
+ // General inversion Rotation+Translation+Scaling
+ calculateModelviewInverse();
+ } else {
+ // Inverse calculation for Rotation+Translation matrix only.
+ calculateModelviewInvNoScaling();
+ }
+
+ // Copying modelview matrix after camera transformations to the camera
+ // matrices.
+ PApplet.arrayCopy(glmodelview, pcamera);
+ PApplet.arrayCopy(glmodelviewInv, pcameraInv);
+ copyGLArrayToPMatrix(pcamera, camera);
+ copyGLArrayToPMatrix(pcameraInv, cameraInv);
+
+ // all done
+ manipulatingCamera = false;
+ scalingDuringCamManip = false;
+ }
+
+ protected void getModelviewMatrix() {
+ if (usingGLMatrixStack) {
+ modelviewStack.get(glmodelview);
+ } else {
+ gl2f.glGetFloatv(GL2.GL_MODELVIEW_MATRIX, glmodelview, 0);
+ }
+ copyGLArrayToPMatrix(glmodelview, modelview);
+ modelviewUpdated = true;
+ }
+
+ // Calculates the inverse of the modelview matrix.
+ // From Matrix4 Matrix4::Inverse in
+ // http://www.geometrictools.com/LibMathematics/Algebra/Wm5Matrix4.inl
+ protected void calculateModelviewInverse() {
+ float[] m = glmodelview;
+ float[] inv = glmodelviewInv;
+
+ float a0 = m[0] * m[5] - m[1] * m[4];
+ float a1 = m[0] * m[6] - m[2] * m[4];
+ float a2 = m[0] * m[7] - m[3] * m[4];
+ float a3 = m[1] * m[6] - m[2] * m[5];
+ float a4 = m[1] * m[7] - m[3] * m[5];
+ float a5 = m[2] * m[7] - m[3] * m[6];
+ float b0 = m[8] * m[13] - m[ 9] * m[12];
+ float b1 = m[8] * m[14] - m[10] * m[12];
+ float b2 = m[8] * m[15] - m[11] * m[12];
+ float b3 = m[9] * m[14] - m[10] * m[13];
+ float b4 = m[9] * m[15] - m[11] * m[13];
+ float b5 = m[10] * m[15] - m[11] * m[14];
+
+ float det = a0 * b5 - a1 * b4 + a2 * b3 + a3 * b2 - a4 * b1 + a5 * b0;
+
+ if (PApplet.abs(det) > 0) {
+ inv[0] = + m[5] * b5 - m[6] * b4 + m[7] * b3;
+ inv[4] = - m[4] * b5 + m[6] * b2 - m[7] * b1;
+ inv[8] = + m[4] * b4 - m[5] * b2 + m[7] * b0;
+ inv[12] = - m[4] * b3 + m[5] * b1 - m[6] * b0;
+ inv[1] = - m[1] * b5 + m[2] * b4 - m[3] * b3;
+ inv[5] = + m[0] * b5 - m[2] * b2 + m[3] * b1;
+ inv[ 9] = - m[0] * b4 + m[1] * b2 - m[3] * b0;
+ inv[13] = + m[0] * b3 - m[1] * b1 + m[2] * b0;
+ inv[2] = + m[13] * a5 - m[14] * a4 + m[15] * a3;
+ inv[6] = - m[12] * a5 + m[14] * a2 - m[15] * a1;
+ inv[10] = + m[12] * a4 - m[13] * a2 + m[15] * a0;
+ inv[14] = - m[12] * a3 + m[13] * a1 - m[14] * a0;
+ inv[3] = - m[9] * a5 + m[10] * a4 - m[11] * a3;
+ inv[7] = + m[8] * a5 - m[10] * a2 + m[11] * a1;
+ inv[11] = - m[8] * a4 + m[ 9] * a2 - m[11] * a0;
+ inv[15] = + m[8] * a3 - m[ 9] * a1 + m[10] * a0;
+
+ float invDet = 1.0f / det;
+ inv[0] *= invDet;
+ inv[1] *= invDet;
+ inv[2] *= invDet;
+ inv[3] *= invDet;
+ inv[4] *= invDet;
+ inv[5] *= invDet;
+ inv[6] *= invDet;
+ inv[7] *= invDet;
+ inv[8] *= invDet;
+ inv[9] *= invDet;
+ inv[10] *= invDet;
+ inv[11] *= invDet;
+ inv[12] *= invDet;
+ inv[13] *= invDet;
+ inv[14] *= invDet;
+ inv[15] *= invDet;
+
+ copyGLArrayToPMatrix(inv, modelviewInv);
+ }
+ }
+
+ // Calculates the inverse of the modelview matrix, assuming that no scaling
+ // transformation was applied, only translations and rotations.
+ // Here is the derivation of the formula:
+ // http://www-graphics.stanford.edu/courses/cs248-98-fall/Final/q4.html
+ protected void calculateModelviewInvNoScaling() {
+ float[] m = glmodelview;
+ float[] inv = glmodelviewInv;
+
+ float ux = m[0];
+ float uy = m[1];
+ float uz = m[2];
+
+ float vx = m[4];
+ float vy = m[5];
+ float vz = m[6];
+
+ float wx = m[8];
+ float wy = m[9];
+ float wz = m[10];
+
+ float tx = m[12];
+ float ty = m[13];
+ float tz = m[14];
+
+ inv[0] = ux;
+ inv[1] = vx;
+ inv[2] = wx;
+ inv[3] = 0.0f;
+
+ inv[4] = uy;
+ inv[5] = vy;
+ inv[6] = wy;
+ inv[7] = 0.0f;
+
+ inv[8] = uz;
+ inv[9] = vz;
+ inv[10] = wz;
+ inv[11] = 0;
+
+ inv[12] = -(ux * tx + uy * ty + uz * tz);
+ inv[13] = -(vx * tx + vy * ty + vz * tz);
+ inv[14] = -(wx * tx + wy * ty + wz * tz);
+ inv[15] = 1.0f;
+
+ copyGLArrayToPMatrix(inv, modelviewInv);
+ }
+
+ /**
+ * Set camera to the default settings.
+ *
+ * Processing camera behavior:
+ *
+ * Camera behavior can be split into two separate components, camera
+ * transformation, and projection. The transformation corresponds to the
+ * physical location, orientation, and scale of the camera. In a physical
+ * camera metaphor, this is what can manipulated by handling the camera body
+ * (with the exception of scale, which doesn't really have a physcial analog).
+ * The projection corresponds to what can be changed by manipulating the lens.
+ *
+ * We maintain separate matrices to represent the camera transform and
+ * projection. An important distinction between the two is that the camera
+ * transform should be invertible, where the projection matrix should not,
+ * since it serves to map three dimensions to two. It is possible to bake the
+ * two matrices into a single one just by multiplying them together, but it
+ * isn't a good idea, since lighting, z-ordering, and z-buffering all demand a
+ * true camera z coordinate after modelview and camera transforms have been
+ * applied but before projection. If the camera transform and projection are
+ * combined there is no way to recover a good camera-space z-coordinate from a
+ * model coordinate.
+ *
+ * Fortunately, there are no functions that manipulate both camera
+ * transformation and projection.
+ *
+ * camera() sets the camera position, orientation, and center of the scene. It
+ * replaces the camera transform with a new one.
+ *
+ * The transformation functions are the same ones used to manipulate the
+ * modelview matrix (scale, translate, rotate, etc.). But they are bracketed
+ * with beginCamera(), endCamera() to indicate that they should apply (in
+ * inverse), to the camera transformation matrix.
+ */
+ public void camera() {
+ camera(cameraX, cameraY, cameraZ, cameraX, cameraY, 0, 0, 1, 0);
+ }
+
+ /**
+ * More flexible method for dealing with camera().
+ *
+ * The actual call is like gluLookat. Here's the real skinny on what does
+ * what:
+ *
+ *
+ * camera(); or
+ * camera(ex, ey, ez, cx, cy, cz, ux, uy, uz);
+ *
+ *
+ * do not need to be called from with beginCamera();/endCamera(); That's
+ * because they always apply to the camera transformation, and they always
+ * totally replace it. That means that any coordinate transforms done before
+ * camera(); in draw() will be wiped out. It also means that camera() always
+ * operates in untransformed world coordinates. Therefore it is always
+ * redundant to call resetMatrix(); before camera(); This isn't technically
+ * true of gluLookat, but it's pretty much how it's used.
+ *
+ * Now, beginCamera(); and endCamera(); are useful if you want to move the
+ * camera around using transforms like translate(), etc. They will wipe out
+ * any coordinate system transforms that occur before them in draw(), but they
+ * will not automatically wipe out the camera transform. This means that they
+ * should be at the top of draw(). It also means that the following:
+ *
+ *
+ * beginCamera();
+ * rotateY(PI / 8);
+ * endCamera();
+ *
+ *
+ * will result in a camera that spins without stopping. If you want to just
+ * rotate a small constant amount, try this:
+ *
+ *
+ * beginCamera();
+ * camera(); // sets up the default view
+ * rotateY(PI / 8);
+ * endCamera();
+ *
+ *
+ * That will rotate a little off of the default view. Note that this is
+ * entirely equivalent to
+ *
+ *
+ * camera(); // sets up the default view
+ * beginCamera();
+ * rotateY(PI / 8);
+ * endCamera();
+ *
+ *
+ * because camera() doesn't care whether or not it's inside a begin/end
+ * clause. Basically it's safe to use camera() or camera(ex, ey, ez, cx, cy,
+ * cz, ux, uy, uz) as naked calls because they do all the matrix resetting
+ * automatically.
+ */
+ public void camera(float eyeX, float eyeY, float eyeZ, float centerX,
+ float centerY, float centerZ, float upX, float upY, float upZ) {
+ eyeY = height - eyeY;
+ centerY = height - centerY;
+
+ // Calculating Z vector
+ float z0 = eyeX - centerX;
+ float z1 = eyeY - centerY;
+ float z2 = eyeZ - centerZ;
+ float mag = PApplet.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
+ if (mag != 0) {
+ z0 /= mag;
+ z1 /= mag;
+ z2 /= mag;
+ }
+
+ // Calculating Y vector
+ float y0 = upX;
+ float y1 = upY;
+ float y2 = upZ;
+
+ // Computing X vector as Y cross Z
+ float x0 = y1 * z2 - y2 * z1;
+ float x1 = -y0 * z2 + y2 * z0;
+ float x2 = y0 * z1 - y1 * z0;
+
+ // Recompute Y = Z cross X
+ y0 = z1 * x2 - z2 * x1;
+ y1 = -z0 * x2 + z2 * x0;
+ y2 = z0 * x1 - z1 * x0;
+
+ // Cross product gives area of parallelogram, which is < 1.0 for
+ // non-perpendicular unit-length vectors; so normalize x, y here:
+ mag = PApplet.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
+ if (mag != 0) {
+ x0 /= mag;
+ x1 /= mag;
+ x2 /= mag;
+ }
+
+ mag = PApplet.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
+ if (mag != 0) {
+ y0 /= mag;
+ y1 /= mag;
+ y2 /= mag;
+ }
+
+ float[] m = glmodelview;
+ m[0] = x0;
+ m[1] = y0;
+ m[2] = z0;
+ m[3] = 0.0f;
+
+ m[4] = x1;
+ m[5] = y1;
+ m[6] = z1;
+ m[7] = 0.0f;
+
+ m[8] = x2;
+ m[9] = y2;
+ m[10] = z2;
+ m[11] = 0;
+
+ m[12] = 0.0f;
+ m[13] = 0.0f;
+ m[14] = 0.0f;
+ m[15] = 1.0f;
+
+ // Translating to the eye position, followed by a translation of height units along the Y axis.
+ // The last one is needed to properly invert coordinate axis of OpenGL so it matches Processing's.
+ float tx = -eyeX;
+ float ty = -eyeY + height;
+ float tz = -eyeZ;
+ m[12] += tx * m[0] + ty * m[4] + tz * m[8];
+ m[13] += tx * m[1] + ty * m[5] + tz * m[9];
+ m[14] += tx * m[2] + ty * m[6] + tz * m[10];
+ m[15] += tx * m[3] + ty * m[7] + tz * m[11];
+
+ // Inverting Y axis.
+ m[4] = -m[4];
+ m[5] = -m[5];
+ m[6] = -m[6];
+ m[7] = -m[7];
+
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ gl2f.glLoadMatrixf(glmodelview, 0);
+ if (usingGLMatrixStack) {
+ modelviewStack.set(glmodelview);
+ }
+ copyGLArrayToPMatrix(glmodelview, modelview);
+ modelviewUpdated = true;
+
+ calculateModelviewInvNoScaling();
+ PApplet.arrayCopy(glmodelview, pcamera);
+ PApplet.arrayCopy(glmodelviewInv, pcameraInv);
+ copyGLArrayToPMatrix(pcamera, camera);
+ copyGLArrayToPMatrix(pcameraInv, cameraInv);
+ }
+
+ /**
+ * Print the current camera matrix.
+ */
+ public void printCamera() {
+ PMatrix3D temp = new PMatrix3D();
+ copyGLArrayToPMatrix(pcamera, temp);
+ temp.print();
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // PROJECTION
+
+ /**
+ * Calls ortho() with the proper parameters for Processing's standard
+ * orthographic projection.
+ */
+ public void ortho() {
+ ortho(0, width, 0, height, cameraNear, cameraFar);
+ }
+
+ /**
+ * Calls ortho() with the specified size of the viewing volume along
+ * the X and Z directions. The near and far clipping planes are taken
+ * from the current camera configuration.
+ */
+ public void ortho(float left, float right, float bottom, float top) {
+ ortho(0, width, 0, height, cameraNear, cameraFar);
+ }
+
+ /**
+ * Sets orthographic projection. The left, right, bottom and top
+ * values refer to the top left corner of the screen, not to the
+ * center or eye of the camera. This is like this because making
+ * it relative to the camera is not very intuitive if we think
+ * of the perspective function, which is also independent of the
+ * camera position.
+ *
+ */
+ public void ortho(float left, float right, float bottom, float top,
+ float near, float far) {
+ left -= width/2;
+ right -= width/2;
+
+ bottom -= height/2;
+ top -= height/2;
+
+ float x = 2.0f / (right - left);
+ float y = 2.0f / (top - bottom);
+ float z = -2.0f / (far - near);
+
+ float tx = -(right + left) / (right - left);
+ float ty = -(top + bottom) / (top - bottom);
+ float tz = -(far + near) / (far - near);
+
+ glprojection[0] = x;
+ glprojection[1] = 0.0f;
+ glprojection[2] = 0.0f;
+ glprojection[3] = 0.0f;
+
+ glprojection[4] = 0.0f;
+ glprojection[5] = y;
+ glprojection[6] = 0.0f;
+ glprojection[7] = 0.0f;
+
+ glprojection[8] = 0;
+ glprojection[9] = 0;
+ glprojection[10] = z;
+ glprojection[11] = 0.0f;
+
+ glprojection[12] = tx;
+ glprojection[13] = ty;
+ glprojection[14] = tz;
+ glprojection[15] = 1.0f;
+
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ gl2f.glLoadMatrixf(glprojection, 0);
+ copyGLArrayToPMatrix(glprojection, projection);
+ projectionUpdated = true;
+
+ // The matrix mode is always MODELVIEW, because the user will be doing
+ // geometrical transformations all the time, projection transformations
+ // only a few times.
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ }
+
+ /**
+ * Calls perspective() with Processing's standard coordinate projection.
+ *
+ * Projection functions:
+ *
+ * - frustrum()
+ *
- ortho()
+ *
- perspective()
+ *
+ * Each of these three functions completely replaces the projection matrix
+ * with a new one. They can be called inside setup(), and their effects will
+ * be felt inside draw(). At the top of draw(), the projection matrix is not
+ * reset. Therefore the last projection function to be called always
+ * dominates. On resize, the default projection is always established, which
+ * has perspective.
+ *
+ * This behavior is pretty much familiar from OpenGL, except where functions
+ * replace matrices, rather than multiplying against the previous.
+ *
+ */
+ public void perspective() {
+ perspective(cameraFOV, cameraAspect, cameraNear, cameraFar);
+ }
+
+ /**
+ * Similar to gluPerspective(). Implementation based on Mesa's glu.c
+ */
+ public void perspective(float fov, float aspect, float zNear, float zFar) {
+ float ymax = zNear * (float) Math.tan(fov / 2);
+ float ymin = -ymax;
+ float xmin = ymin * aspect;
+ float xmax = ymax * aspect;
+ frustum(xmin, xmax, ymin, ymax, zNear, zFar);
+ }
+
+ /**
+ * Same as glFrustum(), except that it wipes out (rather than multiplies
+ * against) the current perspective matrix.
+ *
+ * Implementation based on the explanation in the OpenGL blue book.
+ */
+ public void frustum(float left, float right, float bottom, float top,
+ float znear, float zfar) {
+ float temp, temp2, temp3, temp4;
+ temp = 2.0f * znear;
+ temp2 = right - left;
+ temp3 = top - bottom;
+ temp4 = zfar - znear;
+ glprojection[0] = temp / temp2;
+ glprojection[1] = 0.0f;
+ glprojection[2] = 0.0f;
+ glprojection[3] = 0.0f;
+ glprojection[4] = 0.0f;
+ glprojection[5] = temp / temp3;
+ glprojection[6] = 0.0f;
+ glprojection[7] = 0.0f;
+ glprojection[8] = (right + left) / temp2;
+ glprojection[9] = (top + bottom) / temp3;
+ glprojection[10] = (-zfar - znear) / temp4;
+ glprojection[11] = -1.0f;
+ glprojection[12] = 0.0f;
+ glprojection[13] = 0.0f;
+ glprojection[14] = (-temp * zfar) / temp4;
+ glprojection[15] = 0.0f;
+
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ gl2f.glLoadMatrixf(glprojection, 0);
+ copyGLArrayToPMatrix(glprojection, projection);
+ projectionUpdated = true;
+
+ // The matrix mode is always MODELVIEW, because the user will be doing
+ // geometrical transformations all the time, projection transformations
+ // only a few times.
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ }
+
+ /**
+ * Print the current projection matrix.
+ */
+ public void printProjection() {
+ PMatrix3D temp = new PMatrix3D();
+ copyGLArrayToPMatrix(glprojection, temp);
+ temp.print();
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // SCREEN AND MODEL COORDS
+
+ public float screenX(float x, float y) {
+ return screenX(x, y, 0);
+ }
+
+ public float screenY(float x, float y) {
+ return screenY(x, y, 0);
+ }
+
+ public float screenX(float x, float y, float z) {
+ y = -1 * y; // To take into account Processsing's inverted Y axis with
+ // respect to OpenGL.
+
+ if (!modelviewUpdated) {
+ getModelviewMatrix();
+ }
+
+ if (!projectionUpdated) {
+ getProjectionMatrix();
+ }
+
+ float ax = glmodelview[0] * x + glmodelview[4] * y + glmodelview[8] * z + glmodelview[12];
+ float ay = glmodelview[1] * x + glmodelview[5] * y + glmodelview[9] * z + glmodelview[13];
+ float az = glmodelview[2] * x + glmodelview[6] * y + glmodelview[10] * z + glmodelview[14];
+ float aw = glmodelview[3] * x + glmodelview[7] * y + glmodelview[11] * z + glmodelview[15];
+
+ float ox = glprojection[0] * ax + glprojection[4] * ay + glprojection[8] * az + glprojection[12] * aw;
+ float ow = glprojection[3] * ax + glprojection[7] * ay + glprojection[11] * az + glprojection[15] * aw;
+
+ if (ow != 0) {
+ ox /= ow;
+ }
+ return width * (1 + ox) / 2.0f;
+ }
+
+ public float screenY(float x, float y, float z) {
+ y = -1 * y; // To take into account Processsing's inverted Y axis with
+ // respect to OpenGL.
+
+ if (!modelviewUpdated) {
+ getModelviewMatrix();
+ }
+
+ if (!projectionUpdated) {
+ getProjectionMatrix();
+ }
+
+ float ax = glmodelview[0] * x + glmodelview[4] * y + glmodelview[8] * z + glmodelview[12];
+ float ay = glmodelview[1] * x + glmodelview[5] * y + glmodelview[9] * z + glmodelview[13];
+ float az = glmodelview[2] * x + glmodelview[6] * y + glmodelview[10] * z + glmodelview[14];
+ float aw = glmodelview[3] * x + glmodelview[7] * y + glmodelview[11] * z + glmodelview[15];
+
+ float oy = glprojection[1] * ax + glprojection[5] * ay + glprojection[9] * az + glprojection[13] * aw;
+ float ow = glprojection[3] * ax + glprojection[7] * ay + glprojection[11] * az + glprojection[15] * aw;
+
+ if (ow != 0) {
+ oy /= ow;
+ }
+ return height * (1 + oy) / 2.0f;
+ }
+
+ public float screenZ(float x, float y, float z) {
+ y = -1 * y; // To take into account Processsing's inverted Y axis with
+ // respect to OpenGL.
+
+ if (!modelviewUpdated) {
+ getModelviewMatrix();
+ }
+
+ if (!projectionUpdated) {
+ getProjectionMatrix();
+ }
+
+ float ax = glmodelview[0] * x + glmodelview[4] * y + glmodelview[8] * z + glmodelview[12];
+ float ay = glmodelview[1] * x + glmodelview[5] * y + glmodelview[9] * z + glmodelview[13];
+ float az = glmodelview[2] * x + glmodelview[6] * y + glmodelview[10] * z + glmodelview[14];
+ float aw = glmodelview[3] * x + glmodelview[7] * y + glmodelview[11] * z + glmodelview[15];
+
+ float oz = glprojection[2] * ax + glprojection[6] * ay + glprojection[10] * az + glprojection[14] * aw;
+ float ow = glprojection[3] * ax + glprojection[7] * ay + glprojection[11] * az + glprojection[15] * aw;
+
+ if (ow != 0) {
+ oz /= ow;
+ }
+ return (oz + 1) / 2.0f;
+ }
+
+ public float modelX(float x, float y, float z) {
+ if (!modelviewUpdated) {
+ getModelviewMatrix();
+ }
+
+ float ax = glmodelview[0] * x + glmodelview[4] * y + glmodelview[8] * z + glmodelview[12];
+ float ay = glmodelview[1] * x + glmodelview[5] * y + glmodelview[9] * z + glmodelview[13];
+ float az = glmodelview[2] * x + glmodelview[6] * y + glmodelview[10] * z + glmodelview[14];
+ float aw = glmodelview[3] * x + glmodelview[7] * y + glmodelview[11] * z + glmodelview[15];
+
+ float ox = pcameraInv[0] * ax + pcameraInv[4] * ay + pcameraInv[8] * az + pcameraInv[12] * aw;
+ float ow = pcameraInv[3] * ax + pcameraInv[7] * ay + pcameraInv[11] * az + pcameraInv[15] * aw;
+
+ return (ow != 0) ? ox / ow : ox;
+ }
+
+ public float modelY(float x, float y, float z) {
+ if (!modelviewUpdated) {
+ getModelviewMatrix();
+ }
+
+ float ax = glmodelview[0] * x + glmodelview[4] * y + glmodelview[8] * z + glmodelview[12];
+ float ay = glmodelview[1] * x + glmodelview[5] * y + glmodelview[9] * z + glmodelview[13];
+ float az = glmodelview[2] * x + glmodelview[6] * y + glmodelview[10] * z + glmodelview[14];
+ float aw = glmodelview[3] * x + glmodelview[7] * y + glmodelview[11] * z + glmodelview[15];
+
+ float oy = pcameraInv[1] * ax + pcameraInv[5] * ay + pcameraInv[9] * az + pcameraInv[13] * aw;
+ float ow = pcameraInv[3] * ax + pcameraInv[7] * ay + pcameraInv[11] * az + pcameraInv[15] * aw;
+
+ return (ow != 0) ? oy / ow : oy;
+ }
+
+ public float modelZ(float x, float y, float z) {
+ if (!modelviewUpdated) {
+ getModelviewMatrix();
+ }
+
+ float ax = glmodelview[0] * x + glmodelview[4] * y + glmodelview[8] * z + glmodelview[12];
+ float ay = glmodelview[1] * x + glmodelview[5] * y + glmodelview[9] * z + glmodelview[13];
+ float az = glmodelview[2] * x + glmodelview[6] * y + glmodelview[10] * z + glmodelview[14];
+ float aw = glmodelview[3] * x + glmodelview[7] * y + glmodelview[11] * z + glmodelview[15];
+
+ float oz = pcameraInv[2] * ax + pcameraInv[6] * ay + pcameraInv[10] * az + pcameraInv[14] * aw;
+ float ow = pcameraInv[3] * ax + pcameraInv[7] * ay + pcameraInv[11] * az + pcameraInv[15] * aw;
+
+ return (ow != 0) ? oz / ow : oz;
+ }
+
+ // STYLES
+
+ // public void pushStyle()
+ // public void popStyle()
+ // public void style(PStyle)
+ // public PStyle getStyle()
+ // public void getStyle(PStyle)
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR MODE
+
+ // public void colorMode(int mode)
+ // public void colorMode(int mode, float max)
+ // public void colorMode(int mode, float mx, float my, float mz);
+ // public void colorMode(int mode, float mx, float my, float mz, float ma);
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR CALC
+
+ // protected void colorCalc(int rgb)
+ // protected void colorCalc(int rgb, float alpha)
+ // protected void colorCalc(float gray)
+ // protected void colorCalc(float gray, float alpha)
+ // protected void colorCalc(float x, float y, float z)
+ // protected void colorCalc(float x, float y, float z, float a)
+ // protected void colorCalcARGB(int argb, float alpha)
+
+ //////////////////////////////////////////////////////////////
+
+ // STROKE CAP/JOIN/WEIGHT
+
+ public void strokeWeight(float weight) {
+ this.strokeWeight = weight;
+ }
+
+ public void strokeJoin(int join) {
+ if (join != DEFAULT_STROKE_JOIN) {
+ showMethodWarning("strokeJoin");
+ }
+ }
+
+ public void strokeCap(int cap) {
+ if (cap != DEFAULT_STROKE_CAP) {
+ showMethodWarning("strokeCap");
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // STROKE, TINT, FILL
+
+ // public void noStroke()
+ // public void stroke(int rgb)
+ // public void stroke(int rgb, float alpha)
+ // public void stroke(float gray)
+ // public void stroke(float gray, float alpha)
+ // public void stroke(float x, float y, float z)
+ // public void stroke(float x, float y, float z, float a)
+ // protected void strokeFromCalc()
+
+ // public void noTint()
+ // public void tint(int rgb)
+ // public void tint(int rgb, float alpha)
+ // public void tint(float gray)
+ // public void tint(float gray, float alpha)
+ // public void tint(float x, float y, float z)
+ // public void tint(float x, float y, float z, float a)
+ // protected void tintFromCalc()
+
+ // public void noFill()
+ // public void fill(int rgb)
+ // public void fill(int rgb, float alpha)
+ // public void fill(float gray)
+ // public void fill(float gray, float alpha)
+ // public void fill(float x, float y, float z)
+ // public void fill(float x, float y, float z, float a)
+
+ protected void fillFromCalc() {
+ super.fillFromCalc();
+ calcColorBuffer();
+
+ // A3D uses GL_COLOR_MATERIAL mode, so the ambient and diffuse components
+ // for all vertices are taken from the glColor/color buffer settings.
+ //gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT_AND_DIFFUSE,
+ // colorFloats, 0);
+ }
+
+ protected void setFillColor() {
+ gl2f.glColor4f(fillR, fillG, fillB, fillA);
+ }
+
+ protected void setTintColor() {
+ gl2f.glColor4f(tintR, tintG, tintB, tintA);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // MATERIAL PROPERTIES
+
+ // public void ambient(int rgb) {
+ // super.ambient(rgb);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, colorBuffer, 0);
+ // }
+
+ // public void ambient(float gray) {
+ // super.ambient(gray);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, colorBuffer, 0);
+ // }
+
+ // public void ambient(float x, float y, float z) {
+ // super.ambient(x, y, z);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_AMBIENT, colorBuffer, 0);
+ // }
+
+ protected void ambientFromCalc() {
+ super.ambientFromCalc();
+ calcColorBuffer();
+
+ // A3D uses GL_COLOR_MATERIAL mode, so the ambient and diffuse components
+ // for all vertices are taken from the glColor/color buffer settings.
+ gl2f.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT, colorFloats, 0);
+ }
+
+ // public void specular(int rgb) {
+ // super.specular(rgb);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, colorBuffer, 0);
+ // }
+
+ // public void specular(float gray) {
+ // super.specular(gray);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, colorBuffer, 0);
+ // }
+
+ // public void specular(float x, float y, float z) {
+ // super.specular(x, y, z);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_SPECULAR, colorBuffer, 0);
+ // }
+
+ protected void specularFromCalc() {
+ super.specularFromCalc();
+ calcColorBuffer();
+ gl2f.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, colorFloats, 0);
+ }
+
+ public void shininess(float shine) {
+ super.shininess(shine);
+ gl2f.glMaterialf(GL.GL_FRONT_AND_BACK, GL2.GL_SHININESS, shine);
+ }
+
+ // public void emissive(int rgb) {
+ // super.emissive(rgb);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_EMISSION, colorBuffer, 0);
+ // }
+
+ // public void emissive(float gray) {
+ // super.emissive(gray);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_EMISSION, colorBuffer, 0);
+ // }
+
+ // public void emissive(float x, float y, float z) {
+ // super.emissive(x, y, z);
+ // calcColorBuffer();
+ // gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL.GL_EMISSION, colorBuffer, 0);
+ // }
+
+ protected void emissiveFromCalc() {
+ super.emissiveFromCalc();
+ calcColorBuffer();
+ gl2f.glMaterialfv(GL2.GL_FRONT_AND_BACK, GL2.GL_EMISSION, colorFloats, 0);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // LIGHTING
+
+ /**
+ * Sets up an ambient and directional light using OpenGL. API taken from
+ * PGraphics3D.
+ *
+ *
+ * The Lighting Skinny:
+ * The way lighting works is complicated enough that it's worth
+ * producing a document to describe it. Lighting calculations proceed
+ * pretty much exactly as described in the OpenGL red book.
+ * Light-affecting material properties:
+ * AMBIENT COLOR
+ * - multiplies by light's ambient component
+ * - for believability this should match diffuse color
+ * DIFFUSE COLOR
+ * - multiplies by light's diffuse component
+ * SPECULAR COLOR
+ * - multiplies by light's specular component
+ * - usually less colored than diffuse/ambient
+ * SHININESS
+ * - the concentration of specular effect
+ * - this should be set pretty high (20-50) to see really
+ * noticeable specularity
+ * EMISSIVE COLOR
+ * - constant additive color effect
+ * Light types:
+ * AMBIENT
+ * - one color
+ * - no specular color
+ * - no direction
+ * - may have falloff (constant, linear, and quadratic)
+ * - may have position (which matters in non-constant falloff case)
+ * - multiplies by a material's ambient reflection
+ * DIRECTIONAL
+ * - has diffuse color
+ * - has specular color
+ * - has direction
+ * - no position
+ * - no falloff
+ * - multiplies by a material's diffuse and specular reflections
+ * POINT
+ * - has diffuse color
+ * - has specular color
+ * - has position
+ * - no direction
+ * - may have falloff (constant, linear, and quadratic)
+ * - multiplies by a material's diffuse and specular reflections
+ * SPOT
+ * - has diffuse color
+ * - has specular color
+ * - has position
+ * - has direction
+ * - has cone angle (set to half the total cone angle)
+ * - has concentration value
+ * - may have falloff (constant, linear, and quadratic)
+ * - multiplies by a material's diffuse and specular reflections
+ * Normal modes:
+ * All of the primitives (rect, box, sphere, etc.) have their normals
+ * set nicely. During beginShape/endShape normals can be set by the user.
+ * AUTO-NORMAL
+ * - if no normal is set during the shape, we are in auto-normal mode
+ * - auto-normal calculates one normal per triangle (face-normal mode)
+ * SHAPE-NORMAL
+ * - if one normal is set during the shape, it will be used for
+ * all vertices
+ * VERTEX-NORMAL
+ * - if multiple normals are set, each normal applies to
+ * subsequent vertices
+ * - (except for the first one, which applies to previous
+ * and subsequent vertices)
+ * Efficiency consequences:
+ * There is a major efficiency consequence of position-dependent
+ * lighting calculations per vertex. (See below for determining
+ * whether lighting is vertex position-dependent.) If there is no
+ * position dependency then the only factors that affect the lighting
+ * contribution per vertex are its colors and its normal.
+ * There is a major efficiency win if
+ * 1) lighting is not position dependent
+ * 2) we are in AUTO-NORMAL or SHAPE-NORMAL mode
+ * because then we can calculate one lighting contribution per shape
+ * (SHAPE-NORMAL) or per triangle (AUTO-NORMAL) and simply multiply it
+ * into the vertex colors. The converse is our worst-case performance when
+ * 1) lighting is position dependent
+ * 2) we are in AUTO-NORMAL mode
+ * because then we must calculate lighting per-face * per-vertex.
+ * Each vertex has a different lighting contribution per face in
+ * which it appears. Yuck.
+ * Determining vertex position dependency:
+ * If any of the following factors are TRUE then lighting is
+ * vertex position dependent:
+ * 1) Any lights uses non-constant falloff
+ * 2) There are any point or spot lights
+ * 3) There is a light with specular color AND there is a
+ * material with specular color
+ * So worth noting is that default lighting (a no-falloff ambient
+ * and a directional without specularity) is not position-dependent.
+ * We should capitalize.
+ * Simon Greenwold, April 2005
+ *
+ */
+ public void lights() {
+ enableLighting();
+
+ // need to make sure colorMode is RGB 255 here
+ int colorModeSaved = colorMode;
+ colorMode = RGB;
+
+ lightFalloff(1, 0, 0);
+ lightSpecular(0, 0, 0);
+
+ 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;
+ }
+
+ /**
+ * Disables lighting.
+ */
+ public void noLights() {
+ disableLighting();
+ lightCount = 0;
+ }
+
+ /**
+ * Add an ambient light based on the current color mode.
+ */
+ public void ambientLight(float r, float g, float b) {
+ 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 (!lights) {
+ enableLighting();
+ }
+ if (lightCount == MAX_LIGHTS) {
+ throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
+ }
+ colorCalc(r, g, b);
+ lightDiffuse[lightCount][0] = calcR;
+ lightDiffuse[lightCount][1] = calcG;
+ lightDiffuse[lightCount][2] = calcB;
+ lightDiffuse[lightCount][3] = 1.0f;
+
+ lightType[lightCount] = AMBIENT;
+ lightFalloffConstant[lightCount] = currentLightFalloffConstant;
+ lightFalloffLinear[lightCount] = currentLightFalloffLinear;
+ lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
+ lightPosition[lightCount][0] = x;
+ lightPosition[lightCount][1] = y;
+ lightPosition[lightCount][2] = z;
+ lightPosition[lightCount][3] = 1.0f;
+
+ lightEnable(lightCount);
+ lightAmbient(lightCount);
+ lightPosition(lightCount);
+ lightFalloff(lightCount);
+ lightNoSpot(lightCount);
+ lightNoDiffuse(lightCount);
+ lightNoSpecular(lightCount);
+
+ lightCount++;
+ }
+
+ public void directionalLight(float r, float g, float b, float nx, float ny,
+ float nz) {
+ if (!lights) {
+ enableLighting();
+ }
+ if (lightCount == MAX_LIGHTS) {
+ throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
+ }
+ colorCalc(r, g, b);
+ lightDiffuse[lightCount][0] = calcR;
+ lightDiffuse[lightCount][1] = calcG;
+ lightDiffuse[lightCount][2] = calcB;
+ lightDiffuse[lightCount][3] = 1.0f;
+
+ lightType[lightCount] = DIRECTIONAL;
+ lightFalloffConstant[lightCount] = currentLightFalloffConstant;
+ lightFalloffLinear[lightCount] = currentLightFalloffLinear;
+ lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
+ lightSpecular[lightCount][0] = currentLightSpecular[0];
+ lightSpecular[lightCount][1] = currentLightSpecular[1];
+ lightSpecular[lightCount][2] = currentLightSpecular[2];
+ lightSpecular[lightCount][3] = currentLightSpecular[3];
+
+ float invn = 1.0f / PApplet.dist(0, 0, 0, nx, ny, nz);
+ lightNormal[lightCount][0] = invn * nx;
+ lightNormal[lightCount][1] = invn * ny;
+ lightNormal[lightCount][2] = invn * nz;
+ lightNormal[lightCount][3] = 0.0f;
+
+ lightEnable(lightCount);
+ lightNoAmbient(lightCount);
+ lightDirection(lightCount);
+ lightDiffuse(lightCount);
+ lightSpecular(lightCount);
+ lightFalloff(lightCount);
+ lightNoSpot(lightCount);
+
+ lightCount++;
+ }
+
+ public void pointLight(float r, float g, float b, float x, float y, float z) {
+ if (!lights) {
+ enableLighting();
+ }
+ if (lightCount == MAX_LIGHTS) {
+ throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
+ }
+ colorCalc(r, g, b);
+ lightDiffuse[lightCount][0] = calcR;
+ lightDiffuse[lightCount][1] = calcG;
+ lightDiffuse[lightCount][2] = calcB;
+ lightDiffuse[lightCount][3] = 1.0f;
+
+ lightType[lightCount] = POINT;
+ lightFalloffConstant[lightCount] = currentLightFalloffConstant;
+ lightFalloffLinear[lightCount] = currentLightFalloffLinear;
+ lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
+ lightSpecular[lightCount][0] = currentLightSpecular[0];
+ lightSpecular[lightCount][1] = currentLightSpecular[1];
+ lightSpecular[lightCount][2] = currentLightSpecular[2];
+
+ lightPosition[lightCount][0] = x;
+ lightPosition[lightCount][1] = y;
+ lightPosition[lightCount][2] = z;
+ lightPosition[lightCount][3] = 1.0f;
+
+ lightEnable(lightCount);
+ lightNoAmbient(lightCount);
+ lightPosition(lightCount);
+ lightDiffuse(lightCount);
+ lightSpecular(lightCount);
+ lightFalloff(lightCount);
+ lightNoSpot(lightCount);
+
+ lightCount++;
+ }
+
+ 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 (!lights) {
+ enableLighting();
+ }
+ if (lightCount == MAX_LIGHTS) {
+ throw new RuntimeException("can only create " + MAX_LIGHTS + " lights");
+ }
+ colorCalc(r, g, b);
+ lightDiffuse[lightCount][0] = calcR;
+ lightDiffuse[lightCount][1] = calcG;
+ lightDiffuse[lightCount][2] = calcB;
+ lightDiffuse[lightCount][3] = 1.0f;
+
+ lightType[lightCount] = SPOT;
+ lightFalloffConstant[lightCount] = currentLightFalloffConstant;
+ lightFalloffLinear[lightCount] = currentLightFalloffLinear;
+ lightFalloffQuadratic[lightCount] = currentLightFalloffQuadratic;
+ lightSpecular[lightCount][0] = currentLightSpecular[0];
+ lightSpecular[lightCount][1] = currentLightSpecular[1];
+ lightSpecular[lightCount][2] = currentLightSpecular[2];
+
+ lightPosition[lightCount][0] = x;
+ lightPosition[lightCount][1] = y;
+ lightPosition[lightCount][2] = z;
+ lightPosition[lightCount][3] = 1.0f;
+
+ float invn = 1.0f / PApplet.dist(0, 0, 0, nx, ny, nz);
+ lightNormal[lightCount][0] = invn * nx;
+ lightNormal[lightCount][1] = invn * ny;
+ lightNormal[lightCount][2] = invn * nz;
+ lightNormal[lightCount][3] = 0.0f;
+
+ lightSpotAngle[lightCount] = PApplet.degrees(angle);
+ lightSpotAngleCos[lightCount] = Math.max(0, (float) Math.cos(angle));
+ lightSpotConcentration[lightCount] = concentration;
+
+ lightEnable(lightCount);
+ lightNoAmbient(lightCount);
+ lightPosition(lightCount);
+ lightDirection(lightCount);
+ lightDiffuse(lightCount);
+ lightSpecular(lightCount);
+ lightFalloff(lightCount);
+ lightSpotAngle(lightCount);
+ lightSpotConcentration(lightCount);
+
+ lightCount++;
+ }
+
+ /**
+ * 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) {
+ currentLightFalloffConstant = constant;
+ currentLightFalloffLinear = linear;
+ currentLightFalloffQuadratic = quadratic;
+ }
+
+ /**
+ * Set the specular color of the last light created.
+ */
+ public void lightSpecular(float x, float y, float z) {
+ colorCalc(x, y, z);
+ currentLightSpecular[0] = calcR;
+ currentLightSpecular[1] = calcG;
+ currentLightSpecular[2] = calcB;
+ currentLightSpecular[3] = 1.0f;
+ }
+
+ protected void enableLights() {
+ for (int i = 0; i < lightCount; i++) {
+ lightEnable(i);
+ }
+ }
+
+ protected void disableLights() {
+ for (int i = 0; i < lightCount; i++) {
+ lightDisable(i);
+ }
+ }
+
+ protected void enableLighting() {
+ lights = true;
+ gl2f.glEnable(GL2.GL_LIGHTING);
+ }
+
+ protected void disableLighting() {
+ lights = false;
+ gl2f.glDisable(GL2.GL_LIGHTING);
+ }
+
+ protected void lightAmbient(int num) {
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_AMBIENT, lightDiffuse[num], 0);
+ }
+
+ protected void lightNoAmbient(int num) {
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_AMBIENT, zeroLight, 0);
+ }
+
+ protected void lightNoSpot(int num) {
+ gl2f.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_SPOT_CUTOFF, 180);
+ gl2f.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_SPOT_EXPONENT, 0);
+ }
+
+ protected void lightDiffuse(int num) {
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_DIFFUSE, lightDiffuse[num], 0);
+ }
+
+ protected void lightNoDiffuse(int num) {
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_DIFFUSE, zeroLight, 0);
+ }
+
+ protected void lightDirection(int num) {
+ if (lightType[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"
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_POSITION, lightNormal[num], 0);
+ } else { // spotlight
+ // this one only needs the 3 arg version
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_SPOT_DIRECTION, lightNormal[num], 0);
+ }
+ }
+
+ protected void lightEnable(int num) {
+ gl2f.glEnable(GL2.GL_LIGHT0 + num);
+ }
+
+ protected void lightDisable(int num) {
+ gl2f.glDisable(GL2.GL_LIGHT0 + num);
+ }
+
+ protected void lightFalloff(int num) {
+ gl2f.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_CONSTANT_ATTENUATION,
+ lightFalloffConstant[num]);
+ gl2f.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_LINEAR_ATTENUATION,
+ lightFalloffLinear[num]);
+ gl2f.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_QUADRATIC_ATTENUATION,
+ lightFalloffQuadratic[num]);
+ }
+
+ protected void lightPosition(int num) {
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_POSITION, lightPosition[num], 0);
+ }
+
+ protected void lightSpecular(int num) {
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_SPECULAR, lightSpecular[num], 0);
+ }
+
+ protected void lightNoSpecular(int num) {
+ gl2f.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_SPECULAR, zeroLight, 0);
+ }
+
+ protected void lightSpotAngle(int num) {
+ gl2f.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_SPOT_CUTOFF, lightSpotAngle[num]);
+ }
+
+ protected void lightSpotConcentration(int num) {
+ gl2f.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_SPOT_EXPONENT,
+ lightSpotConcentration[num]);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // BACKGROUND
+
+ protected void backgroundImpl(PImage image) {
+ gl.glClearColor(backgroundR, backgroundG, backgroundB, 1);
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+ set(0, 0, image);
+ }
+
+ protected void backgroundImpl() {
+ gl.glClearColor(backgroundR, backgroundG, backgroundB, 1);
+ gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR MODE
+
+ // colorMode() is inherited from PGraphics.
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR CALC
+
+ // This is the OpenGL complement to the colorCalc() methods.
+
+ /**
+ * Load the calculated color into a pre-allocated array so that it can be
+ * quickly passed over to OpenGL.
+ */
+ protected final void calcColorBuffer() {
+ if (colorFloats == null) {
+ colorFloats = new float[4];
+ }
+ colorFloats[0] = calcR;
+ colorFloats[1] = calcG;
+ colorFloats[2] = calcB;
+ colorFloats[3] = calcA;
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // COLOR METHODS
+
+ // public final int color(int gray)
+ // public final int color(int gray, int alpha)
+ // public final int color(int rgb, float alpha)
+ // public final int color(int x, int y, int z)
+
+ // public final float alpha(int what)
+ // public final float red(int what)
+ // public final float green(int what)
+ // public final float blue(int what)
+ // public final float hue(int what)
+ // public final float saturation(int what)
+ // public final float brightness(int what)
+
+ // public int lerpColor(int c1, int c2, float amt)
+ // static public int lerpColor(int c1, int c2, float amt, int mode)
+
+ //////////////////////////////////////////////////////////////
+
+ // BEGINRAW/ENDRAW
+
+ // beginRaw, endRaw() both inherited.
+
+ //////////////////////////////////////////////////////////////
+
+ // WARNINGS and EXCEPTIONS
+
+ // showWarning() and showException() available from PGraphics.
+
+ /**
+ * Report on anything from glError().
+ * Don't use this inside glBegin/glEnd otherwise it'll
+ * throw an GL_INVALID_OPERATION error.
+ */
+ public void report(String where) {
+ if (!hints[DISABLE_OPENGL_ERROR_REPORT]) {
+ int err = gl.glGetError();
+ if (err != 0) {
+ // TODO: error message without using GLU?
+ // Open Source re-implementation of gluErrorString here:
+ // http://code.google.com/p/glues/source/browse/trunk/glues/source/glues_error.c
+ //String errString = GLU.gluErrorString(err);
+ //String msg = "OpenGL error " + err + " at " + where + ": " + errString;
+ //PGraphics.showWarning(msg);
+ }
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // RENDERER SUPPORT QUERIES
+
+ // public boolean displayable()
+
+ // public boolean dimensional() // from P3D
+
+ //////////////////////////////////////////////////////////////
+
+ // PIMAGE METHODS
+
+ // getImage
+ // setCache, getCache, removeCache
+ // isModified, setModified
+
+ //////////////////////////////////////////////////////////////
+
+ // LOAD/UPDATE PIXELS
+
+ public void loadPixels() {
+ if ((pixels == null) || (pixels.length != width * height)) {
+ pixels = new int[width * height];
+ pixelBuffer = IntBuffer.allocate(pixels.length);
+ pixelBuffer.rewind();
+ }
+
+ gl.glReadPixels(0, 0, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixelBuffer);
+ pixelBuffer.get(pixels);
+ pixelBuffer.rewind();
+
+ // flip vertically (opengl stores images upside down),
+ // and swap RGBA components to ARGB (big endian)
+ int index = 0;
+ int yindex = (height - 1) * width;
+ for (int y = 0; y < height / 2; y++) {
+ if (BIG_ENDIAN) {
+ for (int x = 0; x < width; x++) {
+ int temp = pixels[index];
+ // ignores alpha component, just sets it opaque
+ pixels[index] = 0xff000000 | ((pixels[yindex] >> 8) & 0x00ffffff);
+ pixels[yindex] = 0xff000000 | ((temp >> 8) & 0x00ffffff);
+
+ index++;
+ yindex++;
+ }
+ } else { // LITTLE_ENDIAN, convert ABGR to ARGB
+ for (int x = 0; x < width; x++) {
+ int temp = pixels[index];
+
+ // identical to endPixels because only two
+ // components are being swapped
+ pixels[index] = 0xff000000 | ((pixels[yindex] << 16) & 0xff0000)
+ | (pixels[yindex] & 0xff00) | ((pixels[yindex] >> 16) & 0xff);
+
+ pixels[yindex] = 0xff000000 | ((temp << 16) & 0xff0000)
+ | (temp & 0xff00) | ((temp >> 16) & 0xff);
+
+ index++;
+ yindex++;
+ }
+ }
+ yindex -= width * 2;
+ }
+
+ // When height is an odd number, the middle line needs to be
+ // endian swapped, but not y-swapped.
+ // http://dev.processing.org/bugs/show_bug.cgi?id=944
+ if ((height % 2) == 1) {
+ index = (height / 2) * width;
+ if (BIG_ENDIAN) {
+ for (int x = 0; x < width; x++) {
+ // ignores alpha component, just sets it opaque
+ pixels[index] = 0xff000000 | ((pixels[index] >> 8) & 0x00ffffff);
+ }
+ } else {
+ for (int x = 0; x < width; x++) {
+ pixels[index] = 0xff000000 | ((pixels[index] << 16) & 0xff0000)
+ | (pixels[index] & 0xff00) | ((pixels[index] >> 16) & 0xff);
+ }
+ }
+ }
+ }
+
+ /**
+ * Convert native OpenGL format into palatable ARGB format. This function
+ * leaves alone (ignores) the alpha component. Also flips the image
+ * vertically, since images are upside-down in GL.
+ */
+ static public void nativeToJavaRGB(PImage image) {
+ int index = 0;
+ int yindex = (image.height - 1) * image.width;
+ for (int y = 0; y < image.height / 2; y++) {
+ if (BIG_ENDIAN) {
+ for (int x = 0; x < image.width; x++) {
+ int temp = image.pixels[index];
+ // ignores alpha component, just sets it opaque
+ image.pixels[index] = 0xff000000 | ((image.pixels[yindex] >> 8) & 0x00ffffff);
+ image.pixels[yindex] = 0xff000000 | ((temp >> 8) & 0x00ffffff);
+ index++;
+ yindex++;
+ }
+ } else { // LITTLE_ENDIAN, convert ABGR to ARGB
+ for (int x = 0; x < image.width; x++) {
+ int temp = image.pixels[index];
+
+ // identical to endPixels because only two
+ // components are being swapped
+ image.pixels[index] = 0xff000000
+ | ((image.pixels[yindex] << 16) & 0xff0000)
+ | (image.pixels[yindex] & 0xff00)
+ | ((image.pixels[yindex] >> 16) & 0xff);
+
+ image.pixels[yindex] = 0xff000000 | ((temp << 16) & 0xff0000)
+ | (temp & 0xff00) | ((temp >> 16) & 0xff);
+
+ index++;
+ yindex++;
+ }
+ }
+ yindex -= image.width * 2;
+ }
+ }
+
+ /**
+ * Convert native OpenGL format into palatable ARGB format. This function
+ * leaves alone (ignores) the alpha component. Also flips the image
+ * vertically, since images are upside-down in GL.
+ */
+ static public void nativeToJavaARGB(PImage image) {
+ int index = 0;
+ int yindex = (image.height - 1) * image.width;
+ for (int y = 0; y < image.height / 2; y++) {
+ if (BIG_ENDIAN) {
+ for (int x = 0; x < image.width; x++) {
+ int temp = image.pixels[index];
+ // ignores alpha component, just sets it opaque
+ image.pixels[index] = (image.pixels[yindex] & 0xff000000)
+ | ((image.pixels[yindex] >> 8) & 0x00ffffff);
+ image.pixels[yindex] = (temp & 0xff000000)
+ | ((temp >> 8) & 0x00ffffff);
+ index++;
+ yindex++;
+ }
+ } else { // LITTLE_ENDIAN, convert ABGR to ARGB
+ for (int x = 0; x < image.width; x++) {
+ int temp = image.pixels[index];
+
+ // identical to endPixels because only two
+ // components are being swapped
+ image.pixels[index] = (image.pixels[yindex] & 0xff000000)
+ | ((image.pixels[yindex] << 16) & 0xff0000)
+ | (image.pixels[yindex] & 0xff00)
+ | ((image.pixels[yindex] >> 16) & 0xff);
+
+ image.pixels[yindex] = (temp & 0xff000000)
+ | ((temp << 16) & 0xff0000) | (temp & 0xff00)
+ | ((temp >> 16) & 0xff);
+
+ index++;
+ yindex++;
+ }
+ }
+ yindex -= image.width * 2;
+ }
+ }
+
+ /**
+ * Convert ARGB (Java/Processing) data to native OpenGL format. This function
+ * leaves alone (ignores) the alpha component. Also flips the image
+ * vertically, since images are upside-down in GL.
+ */
+ static public void javaToNativeRGB(PImage image) {
+ int width = image.width;
+ int height = image.height;
+ int pixels[] = image.pixels;
+
+ int index = 0;
+ int yindex = (height - 1) * width;
+ for (int y = 0; y < height / 2; y++) {
+ if (BIG_ENDIAN) {
+ // and convert ARGB back to opengl RGBA components (big endian)
+ for (int x = 0; x < image.width; x++) {
+ int temp = pixels[index];
+ /*
+ * pixels[index] = ((pixels[yindex] >> 24) & 0xff) | ((pixels[yindex]
+ * << 8) & 0xffffff00); pixels[yindex] = ((temp >> 24) & 0xff) |
+ * ((temp << 8) & 0xffffff00);
+ */
+ pixels[index] = ((pixels[yindex] << 8) & 0xffffff00) | 0xff;
+ pixels[yindex] = ((temp << 8) & 0xffffff00) | 0xff;
+
+ index++;
+ yindex++;
+ }
+
+ } else {
+ // convert ARGB back to native little endian ABGR
+ for (int x = 0; x < width; x++) {
+ int temp = pixels[index];
+
+ pixels[index] = 0xff000000 | ((pixels[yindex] << 16) & 0xff0000)
+ | (pixels[yindex] & 0xff00) | ((pixels[yindex] >> 16) & 0xff);
+
+ pixels[yindex] = 0xff000000 | ((temp << 16) & 0xff0000)
+ | (temp & 0xff00) | ((temp >> 16) & 0xff);
+
+ index++;
+ yindex++;
+ }
+ }
+ yindex -= width * 2;
+ }
+ }
+
+ /**
+ * Convert Java ARGB to native OpenGL format. Also flips the image vertically,
+ * since images are upside-down in GL.
+ */
+ static public void javaToNativeARGB(PImage image) {
+ int width = image.width;
+ int height = image.height;
+ int pixels[] = image.pixels;
+
+ int index = 0;
+ int yindex = (height - 1) * width;
+ for (int y = 0; y < height / 2; y++) {
+ if (BIG_ENDIAN) {
+ // and convert ARGB back to opengl RGBA components (big endian)
+ for (int x = 0; x < image.width; x++) {
+ int temp = pixels[index];
+ pixels[index] = ((pixels[yindex] >> 24) & 0xff)
+ | ((pixels[yindex] << 8) & 0xffffff00);
+ pixels[yindex] = ((temp >> 24) & 0xff) | ((temp << 8) & 0xffffff00);
+
+ index++;
+ yindex++;
+ }
+
+ } else {
+ // convert ARGB back to native little endian ABGR
+ for (int x = 0; x < width; x++) {
+ int temp = pixels[index];
+
+ pixels[index] = (pixels[yindex] & 0xff000000)
+ | ((pixels[yindex] << 16) & 0xff0000) | (pixels[yindex] & 0xff00)
+ | ((pixels[yindex] >> 16) & 0xff);
+
+ pixels[yindex] = (pixels[yindex] & 0xff000000)
+ | ((temp << 16) & 0xff0000) | (temp & 0xff00)
+ | ((temp >> 16) & 0xff);
+
+ index++;
+ yindex++;
+ }
+ }
+ yindex -= width * 2;
+ }
+ }
+
+ public void updatePixels() {
+ // flip vertically (opengl stores images upside down),
+
+ int index = 0;
+ int yindex = (height - 1) * width;
+ for (int y = 0; y < height / 2; y++) {
+ if (BIG_ENDIAN) {
+ // and convert ARGB back to opengl RGBA components (big endian)
+ for (int x = 0; x < width; x++) {
+ int temp = pixels[index];
+
+ pixels[index] = ((pixels[yindex] << 8) & 0xffffff00) | 0xff;
+ pixels[yindex] = ((temp << 8) & 0xffffff00) | 0xff;
+
+ index++;
+ yindex++;
+ }
+
+ } else {
+ // convert ARGB back to native little endian ABGR
+ for (int x = 0; x < width; x++) {
+ int temp = pixels[index];
+
+ pixels[index] = 0xff000000 | ((pixels[yindex] << 16) & 0xff0000)
+ | (pixels[yindex] & 0xff00) | ((pixels[yindex] >> 16) & 0xff);
+
+ pixels[yindex] = 0xff000000 | ((temp << 16) & 0xff0000)
+ | (temp & 0xff00) | ((temp >> 16) & 0xff);
+
+ index++;
+ yindex++;
+ }
+ }
+ yindex -= width * 2;
+ }
+
+ pixelBuffer.put(pixels);
+ pixelBuffer.rewind();
+
+ // Copying pixel buffer to screen texture...
+ copyToTexture(pixelBuffer);
+ // ...and drawing the texture to screen.
+ drawTexture(texture, texCrop, 0, 0, width, height);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // LOAD/UPDATE TEXTURE
+
+ public void loadTexture() {
+ if (primarySurface) {
+ loadTextureImpl(POINT);
+ loadPixels();
+ pixelsToTexture();
+ }
+ }
+
+ protected void loadTextureImpl(int sampling) {
+ if (width == 0 || height == 0) return;
+ if (texture == null) {
+ PTexture.Parameters params = PTexture.newParameters(ARGB, sampling);
+ texture = new PTexture(parent, width, height, params);
+ texture.setFlippedY(true);
+ this.setCache(pgl, texture);
+ this.setParams(pgl, params);
+
+ texCrop = new int[4];
+ texCrop[0] = 0;
+ texCrop[1] = 0;
+ texCrop[2] = width;
+ texCrop[3] = height;
+ }
+ }
+
+ // Draws wherever it is in the screen texture right now to the screen.
+ public void updateTexture() {
+ drawTexture(texture, texCrop, 0, 0, width, height);
+ }
+
+ protected void copyToTexture(IntBuffer buffer) {
+ copyToTexture(texture, buffer, 0, 0, width, height);
+ }
+
+ protected void copyFrameToTexture() {
+ // Make sure that the execution off all the openGL commands is
+ // finished before loading the texture.
+ gl.glFinish();
+ loadTexture();
+ }
+
+ protected void pixelsToTexture() {
+ texture.set(pixels);
+ }
+
+ protected void textureToPixels() {
+ texture.get(pixels);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // RESIZE
+
+ public void resize(int wide, int high) {
+ PGraphics.showMethodWarning("resize");
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // GET/SET
+
+ public int get(int x, int y) {
+ if (getsetBuffer == null) {
+ getsetBuffer = IntBuffer.allocate(1);
+ getsetBuffer.rewind();
+ }
+
+ gl.glReadPixels(x, height - y, 1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, getsetBuffer);
+ int getset = getsetBuffer.get(0);
+
+ if (BIG_ENDIAN) {
+ return 0xff000000 | ((getset >> 8) & 0x00ffffff);
+
+ } else {
+ return 0xff000000 | ((getset << 16) & 0xff0000) | (getset & 0xff00)
+ | ((getset >> 16) & 0xff);
+ }
+ }
+
+ // public PImage get(int x, int y, int w, int h)
+
+ protected PImage getImpl(int x, int y, int w, int h) {
+ PImage newbie = parent.createImage(w, h, ARGB);
+ PTexture newbieTex = addTexture(newbie);
+
+ IntBuffer newbieBuffer = IntBuffer.allocate(w * h);
+ gl.glReadPixels(x, height - y, w, -h, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, newbieBuffer);
+ copyToTexture(newbieTex, newbieBuffer, 0, 0, w, h);
+ newbie.loadPixels();
+ newbieTex.get(newbie.pixels);
+
+ return newbie;
+ }
+
+ public PImage get() {
+ return get(0, 0, width, height);
+ }
+
+ public void set(int x, int y, int argb) {
+ int getset = 0;
+
+ if (BIG_ENDIAN) {
+ // convert ARGB to RGBA
+ getset = (argb << 8) | 0xff;
+ } else {
+ // convert ARGB to ABGR
+ getset = (argb & 0xff00ff00) | ((argb << 16) & 0xff0000)
+ | ((argb >> 16) & 0xff);
+ }
+
+ if (getsetBuffer == null) {
+ getsetBuffer = IntBuffer.allocate(1);
+ getsetBuffer.rewind();
+ }
+
+ getsetBuffer.put(0, getset);
+ getsetBuffer.rewind();
+
+ if (getsetTexture == null) {
+ getsetTexture = new PTexture(parent, 1, 1, new PTexture.Parameters(ARGB, POINT));
+ }
+
+ copyToTexture(getsetTexture, getsetBuffer, 0, 0, 1, 1);
+ drawTexture(getsetTexture, 0, 0, 1, 1, x, height - y, 1, 1);
+ }
+
+ /**
+ * Set an image directly to the screen.
+ *
+ */
+ public void set(int x, int y, PImage source) {
+ // We can safely assume that the PImage has a valid associated texture.
+ PTexture tex = (PTexture)source.getCache(pgl);
+ if (tex != null) {
+ int w = source.width;
+ int h = source.height;
+ // The crop region and draw rectangle are given like this to take into account
+ // inverted y-axis in Processin with respect to OpenGL.
+ drawTexture(tex, 0, h, w, -h, x, height - y, w, h);
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // MASK
+
+ public void mask(int alpha[]) {
+ PGraphics.showMethodWarning("mask");
+ }
+
+
+ public void mask(PImage alpha) {
+ PGraphics.showMethodWarning("mask");
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // FILTER
+
+ /**
+ * This is really inefficient and not a good idea in OpenGL. Use get() and
+ * set() with a smaller image area, or call the filter on an image instead,
+ * and then draw that.
+ */
+ public void filter(int kind) {
+ PImage temp = get();
+ temp.filter(kind);
+ set(0, 0, temp);
+ }
+
+ /**
+ * This is really inefficient and not a good idea in OpenGL. Use get() and
+ * set() with a smaller image area, or call the filter on an image instead,
+ * and then draw that.
+ */
+ public void filter(int kind, float param) {
+ PImage temp = get();
+ temp.filter(kind, param);
+ set(0, 0, temp);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ /**
+ * Extremely slow and not optimized, should use GL methods instead. Currently
+ * calls a beginPixels() on the whole canvas, then does the copy, then it
+ * calls endPixels().
+ */
+ // public void copy(int sx1, int sy1, int sx2, int sy2,
+ // int dx1, int dy1, int dx2, int dy2)
+
+ /**
+ * TODO - extremely slow and not optimized. Currently calls a beginPixels() on
+ * the whole canvas, then does the copy, then it calls endPixels().
+ */
+ // public void copy(PImage src,
+ // int sx1, int sy1, int sx2, int sy2,
+ // int dx1, int dy1, int dx2, int dy2)
+
+ //////////////////////////////////////////////////////////////
+
+ // BLEND
+
+ // static public int blendColor(int c1, int c2, int mode)
+
+ // public void blend(PImage src,
+ // int sx, int sy, int dx, int dy, int mode) {
+ // set(dx, dy, PImage.blendColor(src.get(sx, sy), get(dx, dy), mode));
+ // }
+
+ /**
+ * Extremely slow and not optimized, should use GL methods instead. Currently
+ * calls a beginPixels() on the whole canvas, then does the copy, then it
+ * calls endPixels(). Please help fix: Bug 941, Bug 942.
+ */
+ // public void blend(int sx1, int sy1, int sx2, int sy2,
+ // int dx1, int dy1, int dx2, int dy2, int mode) {
+ // loadPixels();
+ // super.blend(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode);
+ // updatePixels();
+ // }
+
+ // public void blend(PImage src,
+ // int sx1, int sy1, int sx2, int sy2,
+ // int dx1, int dy1, int dx2, int dy2, int mode) {
+ // loadPixels();
+ // super.blend(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode);
+ // updatePixels();
+ // }
+
+ /**
+ * Allows to set custom blend modes for the entire scene, using openGL.
+ * Reference article about blending modes:
+ * http://www.pegtop.net/delphi/articles/blendmodes/
+ */
+ public void blend(int mode) {
+ blend = true;
+ blendMode = mode;
+ gl.glEnable(GL.GL_BLEND);
+
+ if (mode == REPLACE) {
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_ONE, GL.GL_ZERO);
+ } else if (mode == BLEND) {
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
+ } else if (mode == ADD) {
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE);
+ } else if (mode == SUBTRACT) {
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ZERO);
+ } else if (mode == LIGHTEST) {
+ if (blendEqSupported) {
+ gl.glBlendEquation(GL2.GL_MAX);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_DST_ALPHA);
+ } else {
+ PGraphics.showWarning("OPENGL2: This blend mode is currently unsupported.");
+ }
+ } else if (mode == DARKEST) {
+ if (blendEqSupported) {
+ gl.glBlendEquation(GL2.GL_MIN);
+ gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_DST_ALPHA);
+ } else {
+ PGraphics.showWarning("OPENGL2: This blend mode is currently unsupported.");
+ }
+ } else if (mode == DIFFERENCE) {
+ if (blendEqSupported) {
+ gl.glBlendEquation(GL.GL_FUNC_REVERSE_SUBTRACT);
+ gl.glBlendFunc(GL.GL_ONE, GL.GL_ONE);
+ } else {
+ PGraphics.showWarning("OPENGL2: This blend mode is currently unsupported.");
+ }
+ } else if (mode == EXCLUSION) {
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE_MINUS_SRC_COLOR);
+ } else if (mode == MULTIPLY) {
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_DST_COLOR, GL.GL_SRC_COLOR);
+ } else if (mode == SCREEN) {
+ if (blendEqSupported) gl.glBlendEquation(GL.GL_FUNC_ADD);
+ gl.glBlendFunc(GL.GL_ONE_MINUS_DST_COLOR, GL.GL_ONE);
+ }
+ // HARD_LIGHT, SOFT_LIGHT, OVERLAY, DODGE, BURN modes cannot be implemented
+ // in fixed-function pipeline because they require conditional blending and
+ // non-linear blending equations.
+ }
+
+
+ public void noBlend() {
+ blend = false;
+ gl.glDisable(GL.GL_BLEND);
+ }
+
+
+ public void textureBlend(int mode) {
+ multitexureBlendMode = mode;
+ }
+
+
+ public void noTextureBlend() {
+ multitexureBlendMode = REPLACE;
+ }
+
+
+ // Some useful info about multitexturing with combiners:
+ // http://www.opengl.org/wiki/Texture_Combiners
+ // http://www.khronos.org/opengles/sdk/1.1/docs/man/glTexEnv.xml
+ // http://techconficio.ca/Blog/files/OpenGL_ES_multiTex_example.html
+ // The GL_DOT3_RGB parameter can be used to implement bump mapping with
+ // the fixed pipeline of GLES 1.1:
+ // http://iphone-3d-programming.labs.oreilly.com/ch08.html
+ // http://nehe.gamedev.net/data/articles/article.asp?article=20
+ // http://www.paulsprojects.net/tutorials/simplebump/simplebump.html
+ // I 'll try to put this functionality in later.
+ // This post explains how to use texture crossbar to properly apply
+ // geometry lighting and tinting to the result of the texure combination.
+ // http://www.imgtec.com/forum/forum_posts.asp?TID=701
+ protected void setMultitextureBlend(PTexture[] textures, int num) {
+ if (2 < num) {
+ PGraphics.showWarning("OPENGL2: multitexture blending currently supports only two textures.");
+ return;
+ }
+
+ if (!texenvCrossbarSupported) {
+ PGraphics.showWarning("OPENGL2: Texture environment crossbar not supported, so the textures won't be affected by tint or light.");
+ // Without texture environment crossbar we have to use the first sampler just to read the first texture,
+ // and the second to do the mixing, in this way we don't have a way to modulate the output of the
+ // texture mixing with the pixel tint or lighting.
+ if (multitexureBlendMode == REPLACE) {
+ // Texture 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ // Simply sample the texture:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);
+ // Texture 1:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE1);
+ gl2f.glBindTexture(textures[1].getGLTarget(), textures[1].getGLID());
+ // Sample the texture, replacing the previous one.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);
+ } else if (multitexureBlendMode == BLEND) {
+ // Texture 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);
+ // Texture 1:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE1);
+ gl2f.glBindTexture(textures[1].getGLTarget(), textures[1].getGLID());
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ // Interpolate RGB with RGB...
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_INTERPOLATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC2_RGB, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // ...using ALPHA of tex1 as interpolation factor.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND2_RGB, GL2.GL_ONE_MINUS_SRC_ALPHA);
+ // Interpolate ALPHA with ALPHA...
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_INTERPOLATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC2_ALPHA, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ // ...using ALPHA of tex1 as interpolation factor.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND2_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
+ } else if (multitexureBlendMode == MULTIPLY) {
+ // Texture 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ // Simply sample the texture.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);
+ // Texture 1:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE1);
+ gl2f.glBindTexture(textures[1].getGLTarget(), textures[1].getGLID());
+ // Combine this texture with the previous one.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ // Modulate (multiply) RGB with RGB:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_MODULATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // Modulate (multiply) ALPHA with ALPHA:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_MODULATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ } else if (multitexureBlendMode == ADD) {
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);
+ // Add RGB with RGB:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE1);
+ gl2f.glBindTexture(textures[1].getGLTarget(), textures[1].getGLID());
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_ADD);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // Add ALPHA with ALPHA:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_ADD);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ } else if (multitexureBlendMode == SUBTRACT) {
+ // Texture 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);
+ // Texture 1:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE1);
+ gl2f.glBindTexture(textures[1].getGLTarget(), textures[1].getGLID());
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ // Subtract RGB with RGB:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_SUBTRACT);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // Subtract ALPHA with ALPHA:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_ADD);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ } else {
+ PGraphics.showWarning("OPENGL2: This blend mode is currently unsupported in multitexture mode.");
+ }
+ } else {
+ // With texture environment crossbar we can do the texture mixing in the first sampler,
+ // and the modulation with the pixel color (which includes tint and light) in the second,
+ // as explained here:
+ // http://www.imgtec.com/forum/forum_posts.asp?TID=701
+ if (multitexureBlendMode == REPLACE) {
+ // Sampler 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ // Replace using texture 1:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ // Replace RGB:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_REPLACE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ // Replace ALPHA with ALPHA:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_REPLACE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ // Sampler 1:
+ modulateWithPrimaryColor(1, textures[1]);
+ } else if (multitexureBlendMode == BLEND) {
+ // Sampler 0: interpolation between textures 0 and 1 using alpha of 1.
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ // Interpolate RGB with RGB...
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_INTERPOLATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC2_RGB, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // ...using ALPHA of tex1 as interpolation factor.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND2_RGB, GL2.GL_ONE_MINUS_SRC_ALPHA);
+ // Interpolate ALPHA with ALPHA...
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_INTERPOLATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC2_ALPHA, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ // ...using ALPHA of tex1 as interpolation factor.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND2_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
+ // Sampler 1:
+ modulateWithPrimaryColor(1, textures[1]);
+ } else if (multitexureBlendMode == MULTIPLY) {
+ // Sampler 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ // Modulate (multiply) texture 0 with texture 1.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ // Modulate RGB with RGB:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_MODULATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // Modulate ALPHA with ALPHA:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_MODULATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ // Sampler 1:
+ modulateWithPrimaryColor(1, textures[1]);
+ } else if (multitexureBlendMode == ADD) {
+ // Sampler 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ // Add texture 0 to texture 1:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_ADD);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // Add ALPHA with ALPHA:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_ADD);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ // Sampler 1:
+ modulateWithPrimaryColor(1, textures[1]);
+ } else if (multitexureBlendMode == SUBTRACT) {
+ // Sampler 0:
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0);
+ gl2f.glBindTexture(textures[0].getGLTarget(), textures[0].getGLID());
+ // Substract texture 1 from texture 0:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ // Subtract RGB with RGB:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_SUBTRACT);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // Subtract ALPHA with ALPHA:
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_SUBTRACT);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_TEXTURE0);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_ALPHA, GL2.GL_TEXTURE1);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_ALPHA, GL2.GL_SRC_ALPHA);
+ // Sampler 1:
+ modulateWithPrimaryColor(1, textures[1]);
+ } else {
+ PGraphics.showWarning("OPENGL2: This blend mode is currently unsupported in multitexture mode.");
+ }
+ }
+
+ // The result of the texture combination will replace the current content of the color buffer.
+ gl2f.glDisable(GL2.GL_BLEND);
+ }
+
+
+ protected void modulateWithPrimaryColor(int unit, PTexture tex) {
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0 + unit);
+ gl2f.glBindTexture(tex.getGLTarget(), tex.getGLID());
+ // Interpolate RGB of previous color (result of blending in sampler 0) with RGB
+ // of primary color (tinted and lit from pixel)...
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_COMBINE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_RGB, GL2.GL_MODULATE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_RGB, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC1_RGB, GL2.GL_PRIMARY_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_RGB, GL2.GL_SRC_COLOR);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND1_RGB, GL2.GL_SRC_COLOR);
+ // Resulting ALPHA is that of mixed textures...
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_COMBINE_ALPHA, GL2.GL_REPLACE);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_SRC0_ALPHA, GL2.GL_PREVIOUS);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_OPERAND0_ALPHA, GL2.GL_SRC_ALPHA);
+ }
+
+
+ protected void clearMultitextureBlend(int num) {
+ for (int i = 0; i < num; i++) {
+ gl2f.glActiveTexture(GL2.GL_TEXTURE0 + i);
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);
+ }
+ // Re-enabling blending.
+ if (blend) {
+ blend(blendMode);
+ } else {
+ noBlend();
+ }
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // SAVE
+
+ // public void save(String filename) // PImage calls loadPixels()
+
+ //////////////////////////////////////////////////////////////
+
+ // SHAPE I/O
+
+ protected String[] getSupportedShapeFormats() {
+ return new String[] { "obj" };
+ }
+
+
+ protected PShape loadShape(String filename, PParameters params) {
+ return new PShape3D(parent, filename, (PShape3D.Parameters)params);
+ }
+
+
+ protected PShape createShape(int size, PParameters params) {
+ return new PShape3D(parent, size, (PShape3D.Parameters)params);
+ }
+
+
+ //////////////////////////////////////////////////////////////
+
+ // TEXTURE UTILS
+
+ /**
+ * This utility method returns the texture associated to the image.
+ * creating and/or updating it if needed.
+ * @param img the image to have a texture metadata associated to it
+ */
+ protected PTexture getTexture(PImage img) {
+ PTexture tex = (PTexture)img.getCache(pgl);
+ if (tex == null) {
+ tex = addTexture(img);
+ } else if (img.isModified()) {
+ updateTexture(img, tex);
+ }
+ return tex;
+ }
+
+ /**
+ * This utility method creates a texture for the provided image, and adds it
+ * to the metadata cache of the image.
+ * @param img the image to have a texture metadata associated to it
+ */
+ protected PTexture addTexture(PImage img) {
+ PTexture.Parameters params = (PTexture.Parameters)img.getParams(this);
+ if (params == null) {
+ params = PTexture.newParameters();
+ img.setParams(this, params);
+ }
+ PTexture tex = new PTexture(img.parent, img.width, img.height, params);
+ img.loadPixels();
+ tex.set(img.pixels);
+ img.setCache(this, tex);
+ return tex;
+ }
+
+
+ protected void updateTexture(PImage img, PTexture tex) {
+ if (tex != null) {
+ int x = img.getModifiedX1();
+ int y = img.getModifiedY1();
+ int w = img.getModifiedX2() - x;
+ int h = img.getModifiedY2() - y;
+ tex.set(img.pixels, x, y, w, h, img.format);
+ }
+ img.setModified(false);
+ }
+
+
+ /** Utility function to render texture. */
+ protected void drawTexture(PTexture tex, int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
+ int[] crop = {x1, y1, w1, h1};
+ drawTexture(tex, crop, x2, y2, w2, h2);
+ }
+
+
+ /** Utility function to render texture. */
+ protected void drawTexture(PTexture tex, int[] crop, int x, int y, int w, int h) {
+ gl.glViewport(0, 0, w, h);
+
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ gl2f.glPushMatrix();
+ gl2f.glLoadIdentity();
+
+ gl2f.glOrthof(0, w, 0, h, -1, 1);
+
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ gl2f.glPushMatrix();
+ gl2f.glLoadIdentity();
+
+ gl.glEnable(tex.getGLTarget());
+ gl.glBindTexture(tex.getGLTarget(), tex.getGLID());
+ gl.glDepthMask(false);
+ gl.glDisable(GL.GL_BLEND);
+
+ // The texels of the texture replace the color of wherever is on the screen.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_REPLACE);
+
+ gl2f.glTranslatef(x, y, 0);
+ gl2f.glScalef(w, h, 1);
+ // Rendering the quad with the appropriate texture coordinates needed for the
+ // specified crop region
+ renderTexQuad(crop[0] / w, crop[1] / h, (crop[0] + crop[2]) / w, (crop[1] + crop[3]) / h);
+
+ // Returning to the default texture environment mode, GL_MODULATE. This allows tinting a texture
+ // with the current fragment color.
+ gl2f.glTexEnvi(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE);
+
+ gl.glBindTexture(tex.getGLTarget(), 0);
+ gl.glDisable(tex.getGLTarget());
+
+ if (hints[DISABLE_DEPTH_MASK]) {
+ gl.glDepthMask(false);
+ } else {
+ gl.glDepthMask(true);
+ }
+
+ if (blend) {
+ blend(blendMode);
+ } else {
+ noBlend();
+ }
+
+ // Restoring viewport.
+ gl.glViewport(0, 0, width, height);
+
+ // Restoring matrices.
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ gl2f.glPopMatrix();
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ gl2f.glPopMatrix();
+ }
+
+ protected void allocateTexQuad() {
+ ByteBuffer vbb = ByteBuffer.allocateDirect(4 * 3 * SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ quadVertexBuffer = vbb.asFloatBuffer();
+
+ ByteBuffer tbb = ByteBuffer.allocateDirect(4 * 2 * SIZEOF_FLOAT);
+ tbb.order(ByteOrder.nativeOrder());
+ quadTexCoordBuffer = tbb.asFloatBuffer();
+
+ quadVertexBuffer.position(0);
+ quadVertexBuffer.put(new float[] {0, 0, 0,
+ 0, 1, 0,
+ 1, 0, 0,
+ 1, 1, 0});
+ }
+
+ protected void renderTexQuad() {
+ renderTexQuad(0, 0, 1, 1);
+ }
+
+ /**
+ * Pushes a normalized (1x1) textured quad to the GPU.
+ */
+ protected void renderTexQuad(float u0, float v0, float u1, float v1) {
+ if (quadVertexBuffer == null) {
+ allocateTexQuad();
+ }
+
+ quadTexCoordBuffer.position(0);
+ quadTexCoordBuffer.put(new float[] {u0, v0,
+ u0, v1,
+ u1, v0,
+ u1, v1});
+
+ gl2f.glEnableClientState(GL2.GL_VERTEX_ARRAY);
+ gl2f.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+
+ quadVertexBuffer.position(0);
+ quadTexCoordBuffer.position(0);
+
+ gl2f.glVertexPointer(3, GL.GL_FLOAT, 0, quadVertexBuffer);
+ gl2f.glTexCoordPointer(2, GL.GL_FLOAT, 0, quadTexCoordBuffer);
+ gl2f.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, 12);
+
+ gl2f.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+ gl2f.glDisableClientState(GL2.GL_VERTEX_ARRAY);
+ }
+
+
+ /**
+ * Utility function to copy buffer to texture.
+ */
+ protected void copyToTexture(PTexture tex, IntBuffer buffer, int x, int y, int w, int h) {
+ gl.glEnable(tex.getGLTarget());
+ gl.glBindTexture(tex.getGLTarget(), tex.getGLID());
+ gl.glTexSubImage2D(tex.getGLTarget(), 0, x, y, w, h, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, buffer);
+ gl.glBindTexture(tex.getGLTarget(), 0);
+ gl.glDisable(tex.getGLTarget());
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // OPENGL ROUTINES
+
+ protected void setSurfaceParams() {
+ // The default shade model is GL_SMOOTH, but we set
+ // here just in case...
+ gl2f.glShadeModel(GL2.GL_SMOOTH);
+
+ // The ambient and diffuse components for each vertex are taken
+ // from the glColor/color buffer setting:
+ gl2f.glEnable(GL2.GL_COLOR_MATERIAL);
+ // For a quick overview of how the lighting model works in OpenGL
+ // see this page:
+ // http://www.sjbaker.org/steve/omniv/opengl_lighting.html
+
+ // Some normal related settings:
+ gl2f.glEnable(GL2.GL_NORMALIZE);
+ gl2f.glEnable(GL2.GL_RESCALE_NORMAL);
+
+ // Light model defaults:
+ // The default opengl ambient light is (0.2, 0.2, 0.2), so
+ // here we set our own default value.
+ gl2f.glLightModelfv(GL2.GL_LIGHT_MODEL_AMBIENT, baseLight, 0);
+ gl2f.glLightModelf(GL2.GL_LIGHT_MODEL_TWO_SIDE, 0);
+ }
+
+ protected void saveGLMatrices() {
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ gl2f.glPushMatrix();
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ gl2f.glPushMatrix();
+ }
+
+ protected void restoreGLMatrices() {
+ // Restoring matrices.
+ gl2f.glMatrixMode(GL2.GL_PROJECTION);
+ gl2f.glPopMatrix();
+ gl2f.glMatrixMode(GL2.GL_MODELVIEW);
+ gl2f.glPopMatrix();
+ }
+
+
+ protected void setDefNormals(float nx, float ny, float nz) {
+ gl2f.glNormal3f(nx, ny, nz);
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // INITIALIZATION ROUTINES
+
+ protected void initPrimary() {
+ GLProfile.initSingleton(true);
+
+ // TODO: when running as applet use:
+ // GLProfile.initSingleton(false);
+ // RCP Application (Applet's, Webstart, Netbeans, ..) using JOGL may not be able
+ // to initialize JOGL before the first UI action.
+
+ pgl = this;
+
+ profile = null;
+
+ profile = GLProfile.get(GLProfile.GL2ES1);
+ pipeline = FIXED;
+
+ /*
+ // Profile auto-selection disabled for the time being.
+ // TODO: Implement programmable pipeline :-)
+ try {
+ profile = GLProfile.get(GLProfile.GL4);
+ pipeline = PROG_GL4;
+ } catch (GLException e) {}
+
+ if (profile == null) {
+ try {
+ profile = GLProfile.get(GLProfile.GL3);
+ pipeline = PROG_GL3;
+ } catch (GLException e) {}
+ }
+
+ if (profile == null) {
+ try {
+ profile = GLProfile.get(GLProfile.GL2ES2);
+ pipeline = PROG_GL2;
+ } catch (GLException e) {}
+ }
+
+ if (profile == null) {
+ try {
+ profile = GLProfile.get(GLProfile.GL2ES1);
+ pipeline = FIXED;
+ } catch (GLException e) {}
+ }
+ */
+
+ if (profile == null) {
+ parent.die("Cannot get a valid OpenGL profile");
+ }
+
+ capabilities = new GLCapabilities(profile);
+ if (!hints[DISABLE_OPENGL_2X_SMOOTH]) {
+ capabilities.setSampleBuffers(true);
+ capabilities.setNumSamples(2);
+ } else if (hints[ENABLE_OPENGL_4X_SMOOTH]) {
+ capabilities.setSampleBuffers(true);
+ capabilities.setNumSamples(4);
+ }
+
+ // Getting the native window:
+ // http://www.java-gaming.org/index.php/topic,21559.0.html
+ AWTGraphicsScreen screen = (AWTGraphicsScreen)AWTGraphicsScreen.createDefault();
+ AWTGraphicsConfiguration config = (AWTGraphicsConfiguration)GraphicsConfigurationFactory
+ .getFactory(AWTGraphicsDevice.class).chooseGraphicsConfiguration(capabilities, capabilities, null, screen);
+ NativeWindow win = NativeWindowFactory.getNativeWindow(parent, config);
+
+ // With the native window we get the drawable and context:
+ GLDrawableFactory factory = GLDrawableFactory.getFactory(profile);
+ drawable = factory.createGLDrawable(win);
+ context = drawable.createContext(null);
+ }
+
+
+ protected void initOffscreen() {
+ // Getting the context and capabilities from the main renderer.
+ pgl = (PGraphicsOpenGL2)parent.g;
+
+ context = pgl.getContext();
+ capabilities = pgl.getCapabilities();
+ drawable = null;
+
+ getGLObjects();
+ loadTextureImpl(BILINEAR);
+
+ // In case of reinitialization (for example, when the smooth level
+ // is changed), we make sure that all the OpenGL resources are
+ // released.
+ if (offscreenFramebuffer != null) {
+ offscreenFramebuffer.delete();
+ offscreenFramebuffer = null;
+ }
+ if (offscreenFramebufferMultisample != null) {
+ offscreenFramebufferMultisample.delete();
+ offscreenFramebufferMultisample = null;
+ }
+
+ boolean opengl2X = !hints[DISABLE_OPENGL_2X_SMOOTH];
+ boolean opengl4X = hints[ENABLE_OPENGL_4X_SMOOTH];
+
+ offscreenMultisample = false;
+ offscreenFramebuffer = new PFramebuffer(parent, texture.getGLWidth(),
+ texture.getGLHeight(), false);
+
+ // We need the GL2GL3 profile to access the glRenderbufferStorageMultisample
+ // function used in multisampled (antialiased) offscreen rendering.
+ if (PGraphicsOpenGL2.fboMultisampleSupported && gl2x != null && (opengl2X || opengl4X)) {
+ int nsamples = 1;
+ if (opengl2X) {
+ nsamples = 2;
+ } else if (opengl4X) {
+ nsamples = 4;
+ }
+ offscreenFramebufferMultisample = new PFramebuffer(parent, texture.getGLWidth(),
+ texture.getGLHeight(), false);
+ try {
+ offscreenFramebufferMultisample.addColorBufferMultisample(nsamples);
+ offscreenMultisample = true;
+ } catch (GLException e) {
+ PGraphics.showWarning("Unable to create antialised offscreen surface.");
+ }
+ }
+
+ if (offscreenMultisample) {
+ // We have multisampling. The fbo with the depth and stencil buffers is the
+ // multisampled fbo, not the regular one. This is because the actual drawing
+ // occurs on the multisampled surface, which is blit into the color buffer
+ // of the regular fbo at endDraw().
+ if (offscreenDepthBits == 24 && offscreenStencilBits == 8) {
+ // A single 24-8 depth-stencil buffer is created here.
+ offscreenFramebufferMultisample.addDepthStencilBuffer();
+ } else {
+ // Separate depth and stencil buffers with custom number of bits are
+ // created here. But there is no guarantee that this will lead to a valid
+ // offscreen surface.
+ offscreenFramebufferMultisample.addDepthBuffer(offscreenDepthBits);
+ if (0 < offscreenStencilBits) {
+ offscreenFramebufferMultisample.addStencilBuffer(offscreenStencilBits);
+ }
+ }
+ offscreenFramebufferMultisample.clear();
+
+ offscreenFramebuffer.setColorBuffer(texture);
+ } else {
+ // No multisampling.
+ offscreenFramebuffer.setColorBuffer(texture);
+
+ if (offscreenDepthBits == 24 && offscreenStencilBits == 8) {
+ // A single 24-8 depth-stencil buffer is created here.
+ offscreenFramebuffer.addDepthStencilBuffer();
+ } else {
+ // Separate depth and stencil buffers with custom number of bits are
+ // created here. But there is no guarantee that this will lead to a valid
+ // offscreen surface.
+ offscreenFramebuffer.addDepthBuffer(offscreenDepthBits);
+ if (0 < offscreenStencilBits) {
+ offscreenFramebuffer.addStencilBuffer(offscreenStencilBits);
+ }
+ }
+ }
+
+ offscreenFramebuffer.clear();
+ }
+
+ protected void getGLObjects() {
+ gl = context.getGL();
+ if (pipeline == PROG_GL4) {
+ gl4p = gl.getGL4();
+ gl3p = gl4p;
+ gl2p = gl4p;
+ gl2f = null;
+ } else if (pipeline == PROG_GL3) {
+ gl4p = null;
+ gl3p = gl.getGL3();
+ gl2p = gl3p;
+ gl2f = null;
+ } else if (pipeline == PROG_GL2) {
+ gl4p = null;
+ gl3p = null;
+ gl2p = gl.getGL2ES2();
+ gl2f = null;
+ } else if (pipeline == FIXED) {
+ gl4p = null;
+ gl3p = null;
+ gl2p = null;
+ gl2f = gl.getGL2ES1();
+ }
+
+ try {
+ gl2x = gl.getGL2GL3();
+ } catch (GLException e) {}
+ }
+
+
+ protected void getGLParameters() {
+ OPENGL_VENDOR = gl.glGetString(GL.GL_VENDOR);
+ OPENGL_RENDERER = gl.glGetString(GL.GL_RENDERER);
+ OPENGL_VERSION = gl.glGetString(GL.GL_VERSION);
+
+ // Better way to check for extensions and related functions (taken from jMonkeyEngine):
+ // renderbufferStorageMultisample = gl.isExtensionAvailable("GL_EXT_framebuffer_multisample") &&
+ // gl.isFunctionAvailable("glRenderbufferStorageMultisample");
+ // For more details on GL properties initialiation in jMonkey using JOGL2, take a look at:
+ // http://code.google.com/p/jmonkeyengine/source/browse/branches/jme3/src/jogl2/com/jme3/renderer/jogl/JoglRenderer.java
+
+ npotTexSupported = false;
+ mipmapGeneration = false;
+ matrixGetSupported = false;
+ texenvCrossbarSupported = false;
+ vboSupported = false;
+ fboSupported = false;
+ fboMultisampleSupported = false;
+ String extensions = gl.glGetString(GL.GL_EXTENSIONS);
+ if (-1 < extensions.indexOf("texture_non_power_of_two")) {
+ npotTexSupported = true;
+ }
+ if (-1 < extensions.indexOf("generate_mipmap")) {
+ mipmapGeneration = true;
+ }
+ if (-1 < extensions.indexOf("matrix_get")) {
+ matrixGetSupported = true;
+ }
+ if (-1 < extensions.indexOf("texture_env_crossbar")) {
+ texenvCrossbarSupported = true;
+ }
+ if (-1 < extensions.indexOf("vertex_buffer_object")) {
+ vboSupported = true;
+ }
+ if (-1 < extensions.indexOf("framebuffer_object")) {
+ fboSupported = true;
+ }
+ if (-1 < extensions.indexOf("framebuffer_multisample")) {
+ fboMultisampleSupported = true;
+ }
+
+ blendEqSupported = true;
+
+ usingGLMatrixStack = !matrixGetSupported;
+
+ int temp[] = new int[2];
+
+ gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, temp, 0);
+ maxTextureSize = temp[0];
+
+ gl.glGetIntegerv(GL.GL_ALIASED_LINE_WIDTH_RANGE, temp, 0);
+ maxLineWidth = temp[1];
+
+ gl.glGetIntegerv(GL.GL_ALIASED_POINT_SIZE_RANGE, temp, 0);
+ maxPointSize = temp[1];
+
+ // The maximum number of texture units only makes sense in the
+ // fixed pipeline.
+ gl.glGetIntegerv(GL2.GL_MAX_TEXTURE_UNITS, temp, 0);
+ maxTextureUnits = PApplet.min(MAX_TEXTURES, temp[0]);
+
+ glparamsRead = true;
+ }
+
+ //////////////////////////////////////////////////////////////
+
+ // UTILITY INNER CLASSES
+
+ /**
+ * This class encapsulates the drawing state in Processing.
+ */
+ protected class DrawingState {
+ int tMode0;
+ boolean auto0;
+ boolean stroke0;
+ int cMode0;
+ boolean merge0;
+ float specularR0, specularG0, specularB0;
+ float ambientR0, ambientG0, ambientB0;
+ boolean fill0;
+ float fillR0, fillG0, fillB0, fillA0;
+ int fillRi0, fillGi0, fillBi0, fillAi0;
+ int fillColor0;
+ boolean fillAlpha0;
+ boolean tint0;
+ float tintR0, tintG0, tintB0, tintA0;
+ int tintRi0, tintGi0, tintBi0, tintAi0;
+ int tintColor0;
+ boolean tintAlpha0;
+ float shininess0;
+
+ DrawingState() {}
+
+ void save() {
+ tMode0 = textureMode;
+ auto0 = autoNormal;
+ stroke0 = stroke;
+ cMode0 = colorMode;
+ merge0 = mergeRecShapes;
+
+ // Saving current colors.
+ specularR0 = specularR;
+ specularG0 = specularG;
+ specularB0 = specularB;
+
+ ambientR0 = ambientR;
+ ambientG0 = ambientG;
+ ambientB0 = ambientB;
+
+ fill0 = fill;
+ fillR0 = fillR;
+ fillG0 = fillG;
+ fillB0 = fillB;
+ fillA0 = fillA;
+ fillRi0 = fillRi;
+ fillGi0 = fillGi;
+ fillBi0 = fillBi;
+ fillAi0 = fillAi;
+ fillColor0 = fillColor;
+ fillAlpha0 = fillAlpha;
+
+ tint0 = tint;
+ tintR0 = tintR;
+ tintG0 = tintG;
+ tintB0 = tintB;
+ tintA0 = tintA;
+ tintRi0 = tintRi;
+ tintGi0 = tintGi;
+ tintBi0 = tintBi;
+ tintAi0 = tintAi;
+ tintColor0 = tintColor;
+ tintAlpha0 = tintAlpha;
+
+ shininess0 = shininess;
+ }
+
+ void restore() {
+ textureMode = tMode0;
+ colorMode = cMode0;
+ autoNormal = auto0;
+ stroke = stroke0;
+ mergeRecShapes = merge0;
+
+ // Restore colors
+ calcR = specularR0;
+ calcG = specularG0;
+ calcB = specularB0;
+ specularFromCalc();
+
+ calcR = ambientR0;
+ calcG = ambientG0;
+ calcB = ambientB0;
+ ambientFromCalc();
+
+ if (!fill0) {
+ noFill();
+ } else {
+ calcR = fillR0;
+ calcG = fillG0;
+ calcB = fillB0;
+ calcA = fillA0;
+ calcRi = fillRi0;
+ calcGi = fillGi0;
+ calcBi = fillBi0;
+ calcAi = fillAi0;
+ calcColor = fillColor0;
+ calcAlpha = fillAlpha0;
+ fillFromCalc();
+ }
+
+ if (!tint0) {
+ noTint();
+ } else {
+ calcR = tintR0;
+ calcG = tintG0;
+ calcB = tintB0;
+ calcA = tintA0;
+ calcRi = tintRi0;
+ calcGi = tintGi0;
+ calcBi = tintBi0;
+ calcAi = tintAi0;
+ calcColor = tintColor0;
+ calcAlpha = tintAlpha0;
+ tintFromCalc();
+ }
+
+ shininess(shininess0);
+ }
+ }
+
+
+ /**
+ * This class encapsulates a static matrix stack that can be used
+ * to mirror the changes in OpenGL matrices.
+ */
+ protected class GLMatrixStack {
+ protected Stack matrixStack;
+ protected float[] current;
+
+ public GLMatrixStack() {
+ matrixStack = new Stack();
+ current = new float[16];
+ set(1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1);
+ }
+
+ public void push() {
+ float[] mat = new float[16];
+ PApplet.arrayCopy(current, mat);
+ matrixStack.push(mat);
+ }
+
+ public void pop() {
+ try {
+ float[] mat = matrixStack.pop();
+ PApplet.arrayCopy(mat, current);
+ } catch (EmptyStackException e) {
+ PGraphics.showWarning("OPENGL2: Empty modelview stack");
+ }
+ }
+
+ public void mult(float[] mat) {
+ mult(mat[0], mat[4], mat[8], mat[12],
+ mat[1], mat[5], mat[9], mat[13],
+ mat[2], mat[6], mat[10], mat[14],
+ mat[3], mat[7], mat[11], mat[15]);
+ }
+
+ public void mult(float n0, float n4, float n8, float n12,
+ float n1, float n5, float n9, float n13,
+ float n2, float n6, float n10, float n14,
+ float n3, float n7, float n11, float n15) {
+ float r0 = current[0]*n0 + current[4]*n1 + current[8]*n2 + current[12]*n3;
+ float r4 = current[0]*n4 + current[4]*n5 + current[8]*n6 + current[12]*n7;
+ float r8 = current[0]*n8 + current[4]*n9 + current[8]*n10 + current[12]*n11;
+ float r12 = current[0]*n12 + current[4]*n13 + current[8]*n14 + current[12]*n15;
+
+ float r1 = current[1]*n0 + current[5]*n1 + current[9]*n2 + current[13]*n3;
+ float r5 = current[1]*n4 + current[5]*n5 + current[9]*n6 + current[13]*n7;
+ float r9 = current[1]*n8 + current[5]*n8 + current[9]*n10 + current[13]*n11;
+ float r13 = current[1]*n12 + current[5]*n13 + current[9]*n14 + current[13]*n15;
+
+ float r2 = current[2]*n0 + current[6]*n1 + current[10]*n2 + current[14]*n3;
+ float r6 = current[2]*n4 + current[6]*n5 + current[10]*n6 + current[14]*n7;
+ float r10 = current[2]*n8 + current[6]*n9 + current[10]*n10 + current[14]*n11;
+ float r14 = current[2]*n12 + current[6]*n13 + current[10]*n14 + current[14]*n15;
+
+ float r3 = current[3]*n0 + current[7]*n1 + current[11]*n2 + current[15]*n3;
+ float r7 = current[3]*n4 + current[7]*n5 + current[11]*n6 + current[15]*n7;
+ float r11 = current[3]*n8 + current[7]*n9 + current[11]*n10 + current[15]*n11;
+ float r15 = current[3]*n12 + current[7]*n13 + current[11]*n14 + current[15]*n15;
+
+ current[0] = r0; current[4] = r4; current[8] = r8; current[12] = r12;
+ current[1] = r1; current[5] = r5; current[9] = r9; current[13] = r13;
+ current[2] = r2; current[6] = r6; current[10] = r10; current[14] = r14;
+ current[3] = r3; current[7] = r7; current[11] = r11; current[15] = r15;
+ }
+
+ public void get(float[] mat) {
+ PApplet.arrayCopy(current, mat);
+ }
+
+ public void set(float[] mat) {
+ PApplet.arrayCopy(mat, current);
+ }
+
+ public void set(float n0, float n4, float n8, float n12,
+ float n1, float n5, float n9, float n13,
+ float n2, float n6, float n10, float n14,
+ float n3, float n7, float n11, float n15) {
+ current[0] = n0; current[4] = n4; current[8] = n8; current[12] = n12;
+ current[1] = n1; current[5] = n5; current[9] = n9; current[13] = n13;
+ current[2] = n2; current[6] = n6; current[10] = n10; current[14] = n14;
+ current[3] = n3; current[7] = n7; current[11] = n11; current[15] = n15;
+ }
+
+ public void translate(float tx, float ty, float tz) {
+ current[12] += tx*current[0] + ty*current[4] + tz*current[8];
+ current[13] += tx*current[1] + ty*current[5] + tz*current[9];
+ current[14] += tx*current[2] + ty*current[6] + tz*current[10];
+ current[15] += tx*current[3] + ty*current[7] + tz*current[11];
+ }
+
+ public void rotate(float angle, float rx, float ry, float rz) {
+ float c = PApplet.cos(angle);
+ float s = PApplet.sin(angle);
+ float t = 1.0f - c;
+
+ mult((t*rx*rx) + c, (t*rx*ry) - (s*rz), (t*rx*rz) + (s*ry), 0,
+ (t*rx*ry) + (s*rz), (t*ry*ry) + c, (t*ry*rz) - (s*rx), 0,
+ (t*rx*rz) - (s*ry), (t*ry*rz) + (s*rx), (t*rz*rz) + c, 0,
+ 0, 0, 0, 1);
+ }
+
+ public void scale(float sx, float sy, float sz) {
+ current[0] *= sx; current[4] *= sy; current[8] *= sz;
+ current[1] *= sx; current[5] *= sy; current[9] *= sz;
+ current[2] *= sx; current[6] *= sy; current[10] *= sz;
+ current[3] *= sx; current[7] *= sy; current[11] *= sz;
+ }
+ }
+}
diff --git a/java/libraries/opengl2/src/processing/opengl2/PShape3D.java b/java/libraries/opengl2/src/processing/opengl2/PShape3D.java
new file mode 100644
index 000000000..0af31593e
--- /dev/null
+++ b/java/libraries/opengl2/src/processing/opengl2/PShape3D.java
@@ -0,0 +1,2852 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2010 Ben Fry and Casey Reas
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License version 2.1 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.opengl2;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+import javax.media.opengl.GL2ES1;
+import processing.core.PApplet;
+import processing.core.PConstants;
+import processing.core.PGraphics;
+import processing.core.PImage;
+import processing.core.PParameters;
+import processing.core.PShape;
+import processing.core.PVector;
+import java.nio.FloatBuffer;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.io.BufferedReader;
+
+/**
+ * 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) in GPU memory for very fast access.
+ * OBJ loading implemented using code from Saito's OBJLoader library (http://code.google.com/p/saitoobjloader/)
+ * and OBJReader from Ahmet Kizilay (http://www.openprocessing.org/visuals/?visualID=191).
+ * By Andres Colubri
+ *
+ */
+public class PShape3D extends PShape implements PConstants {
+ protected PApplet papplet;
+ protected GL2ES1 gl;
+ protected PGraphicsOpenGL2 pgl;
+
+ // Element types handled by PShape3D (vertices, normals, color, texture coordinates).
+ protected static final int VERTICES = 0;
+ protected static final int NORMALS = 1;
+ protected static final int COLORS = 2;
+ protected static final int TEXCOORDS = 3;
+
+ // ROOT shape properties:
+
+ // STATIC, DYNAMIC, or STREAM
+ protected int glUsage;
+
+ // Number of texture buffers currently in use:
+ protected int numTexBuffers;
+
+ // The OpenGL IDs for the different VBOs
+ protected int glVertexBufferID;
+ protected int glColorBufferID;
+ protected int glNormalBufferID;
+ protected int[] glTexCoordBufferID;
+
+ // The float buffers (directly allocated) used to put data into the VBOs
+ protected FloatBuffer vertexBuffer;
+ protected FloatBuffer colorBuffer;
+ protected FloatBuffer normalBuffer;
+ protected FloatBuffer texCoordBuffer;
+
+ // Public arrays for setting/getting vertices, colors, normals, and
+ // texture coordinates when using loadVertices/updateVertices,
+ // loadNormals/updateNormals, etc. This is modeled following the
+ // loadPixels/updatePixels API for setting/getting pixel data in
+ // PImage.
+ public float[] vertices;
+ public float[] colors;
+ public float[] normals;
+ public float[] texcoords;
+
+ // To put the texture coordinate values adjusted according to texture
+ // flipping mode, max UV range, etc.
+ protected float[] convTexcoords;
+ // The array of arrays holding the texture coordinates for all texture units.
+ protected float[][] allTexcoords;
+
+ // Child PShape3D associated to each vertex in the model.
+ protected PShape3D[] vertexChild;
+
+ // Some utility arrays.
+ protected boolean[] texCoordSet;
+
+ protected boolean autoBounds = true;
+
+ // For OBJ loading. Only used by the root shape.
+ boolean readFromOBJ = false;
+ ArrayList objVertices;
+ ArrayList objNormal;
+ ArrayList objTexCoords;
+ ArrayList objFaces;
+ ArrayList objMaterials;
+
+ // GEOMETRY shape properties:
+
+ // Draw mode, point sprites and textures.
+ // Stroke weight is inherited from PShape.
+ protected int glMode;
+ protected boolean pointSprites;
+ protected PImage[] textures;
+ protected float maxSpriteSize = PGraphicsOpenGL2.maxPointSize;
+ // Coefficients for point sprite distance attenuation function.
+ // These default values correspond to the constant sprite size.
+ protected float spriteDistAtt[] = { 1.0f, 0.0f, 0.0f };
+ // Used in the drawGeometry() method.
+ protected PTexture[] renderTextures;
+
+ // GROUP and GEOMETRY properties:
+
+ // The root group shape.
+ protected PShape3D root;
+
+ // Element type, vertex indices and texture units being currently updated.
+ protected int updateElement;
+ protected int updateTexunit;
+ protected int firstUpdateIdx;
+ protected int lastUpdateIdx;
+
+ // first and last vertex in the shape. For the root group shape, these are 0 and
+ // vertexCount - 1.
+ protected int firstVertex;
+ protected int lastVertex;
+
+ // Bounding box (defined for all shapes, group and geometry):
+ public float xmin, xmax;
+ public float ymin, ymax;
+ public float zmin, zmax;
+
+ ////////////////////////////////////////////////////////////
+
+ // Constructors.
+
+ public PShape3D() {
+ this.papplet = null;
+ pgl = null;
+
+ glVertexBufferID = 0;
+ glColorBufferID = 0;
+ glNormalBufferID = 0;
+ glTexCoordBufferID = null;
+ }
+
+ public PShape3D(PApplet parent) {
+ this();
+ this.papplet = parent;
+ pgl = (PGraphicsOpenGL2)parent.g;
+ this.family = PShape.GROUP;
+ this.name = "root";
+ this.root = this;
+ }
+
+ public PShape3D(PApplet parent, int numVert) {
+ this(parent, numVert, new Parameters());
+ }
+
+ public PShape3D(PApplet parent, String filename, Parameters params) {
+ this.papplet = parent;
+ pgl = (PGraphicsOpenGL2)parent.g;
+
+ this.family = PShape.GROUP;
+ this.name = "root";
+ this.root = this;
+ glVertexBufferID = 0;
+ glColorBufferID = 0;
+ glNormalBufferID = 0;
+ glTexCoordBufferID = null;
+
+ initShapeOBJ(filename, params);
+ }
+
+
+ public PShape3D(PApplet parent, int size, Parameters params) {
+ this.papplet = parent;
+ pgl = (PGraphicsOpenGL2)parent.g;
+
+ this.family = PShape.GROUP;
+ this.name = "root";
+ this.root = this;
+ glVertexBufferID = 0;
+ glColorBufferID = 0;
+ glNormalBufferID = 0;
+ glTexCoordBufferID = null;
+
+ initShape(size, params);
+ }
+
+
+ public void delete() {
+ if (root != this) return; // Can be done only from the root shape.
+
+ deleteVertexBuffer();
+ deleteColorBuffer();
+ deleteTexCoordBuffer();
+ deleteNormalBuffer();
+ }
+
+ ////////////////////////////////////////////////////////////
+
+ // load/update/set/get methods
+
+
+ public void loadVertices() {
+ loadVertices(firstVertex, lastVertex);
+ }
+
+
+ public void loadVertices(int first, int last) {
+ if (last < first || first < firstVertex || lastVertex < last) {
+ PGraphics.showWarning("PShape3D: wrong vertex index");
+ updateElement = -1;
+ return;
+ }
+
+ if (updateElement != -1) {
+ PGraphics.showWarning("PShape3D: can load only one type of data at the time");
+ return;
+ }
+
+ updateElement = VERTICES;
+ firstUpdateIdx = first;
+ lastUpdateIdx = last;
+
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glVertexBufferID);
+
+ vertexBuffer = gl.glMapBuffer(GL.GL_ARRAY_BUFFER, GL.GL_WRITE_ONLY).asFloatBuffer();
+
+ // * Possible optimization:
+ // int offset = firstUpdateIdx * 3;
+ // int size = (lastUpdateIdx - firstUpdateIdx + 1) * 3;
+ //vertexBuffer = gl.glMapBufferRange(GL.GL_ARRAY_BUFFER, offset, size, GL.GL_WRITE_ONLY).asFloatBuffer();
+ // if using this, the vertexBuffer.put(vertices) should be start at 0. I think.
+ // * Another optimization: use BufferData instead of Map to replace an ENTIRE buffer.
+ }
+
+
+ public void updateVertices() {
+ if (updateElement == VERTICES) {
+ int offset = firstUpdateIdx * 3;
+ int size = (lastUpdateIdx - firstUpdateIdx + 1) * 3;
+
+ if (root.autoBounds) {
+ updateBounds(firstUpdateIdx, lastUpdateIdx);
+ }
+
+ vertexBuffer.position(0);
+ vertexBuffer.put(vertices, offset, size);
+ vertexBuffer.flip();
+
+ gl.glUnmapBuffer(GL.GL_ARRAY_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+
+ updateElement = -1;
+ } else {
+ PGraphics.showWarning("PShape3D: need to call loadVertices() first");
+ }
+ }
+
+
+ public void loadColors() {
+ loadColors(firstVertex, lastVertex);
+ }
+
+
+ public void loadColors(int first, int last) {
+ if (last < first || first < firstVertex || lastVertex < last) {
+ PGraphics.showWarning("PShape3D: wrong vertex index");
+ updateElement = -1;
+ return;
+ }
+
+ if (updateElement != -1) {
+ PGraphics.showWarning("PShape3D: can load only one type of data at the time");
+ return;
+ }
+
+ updateElement = COLORS;
+ firstUpdateIdx = first;
+ lastUpdateIdx = last;
+
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glColorBufferID);
+ colorBuffer = gl.glMapBuffer(GL.GL_ARRAY_BUFFER, GL.GL_WRITE_ONLY).asFloatBuffer();
+ }
+
+
+ public void updateColors() {
+ if (updateElement == COLORS) {
+ int offset = firstUpdateIdx * 4;
+ int size = (lastUpdateIdx - firstUpdateIdx + 1) * 4;
+
+ colorBuffer.position(0);
+ colorBuffer.put(colors, offset, size);
+ colorBuffer.flip();
+
+ gl.glUnmapBuffer(GL.GL_ARRAY_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+
+ updateElement = -1;
+ } else {
+ PGraphics.showWarning("PShape3D: need to call loadColors() first");
+ }
+ }
+
+
+ public void loadNormals() {
+ loadNormals(firstVertex, lastVertex);
+ }
+
+
+ public void loadNormals(int first, int last) {
+ if (last < first || first < firstVertex || lastVertex < last) {
+ PGraphics.showWarning("PShape3D: wrong vertex index");
+ updateElement = -1;
+ return;
+ }
+
+ if (updateElement != -1) {
+ PGraphics.showWarning("PShape3D: can load only one type of data at the time");
+ return;
+ }
+
+ updateElement = NORMALS;
+ firstUpdateIdx = first;
+ lastUpdateIdx = last;
+
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glNormalBufferID);
+ normalBuffer = gl.glMapBuffer(GL.GL_ARRAY_BUFFER, GL.GL_WRITE_ONLY).asFloatBuffer();
+ }
+
+
+ public void updateNormals() {
+ if (updateElement == NORMALS) {
+ int offset = firstUpdateIdx * 3;
+ int size = (lastUpdateIdx - firstUpdateIdx + 1) * 3;
+
+ normalBuffer.position(0);
+ normalBuffer.put(normals, offset, size);
+ normalBuffer.flip();
+
+ gl.glUnmapBuffer(GL.GL_ARRAY_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+
+ updateElement = -1;
+ } else {
+ PGraphics.showWarning("PShape3D: need to call loadNormals() first");
+ }
+ }
+
+
+ public void loadTexcoords() {
+ loadTexcoords(0);
+ }
+
+
+ public void loadTexcoords(int unit) {
+ loadTexcoords(unit, firstVertex, lastVertex);
+ }
+
+
+ protected void loadTexcoords(int unit, int first, int last) {
+ if (last < first || first < firstVertex || lastVertex < last) {
+ PGraphics.showWarning("PShape3D: wrong vertex index");
+ updateElement = -1;
+ return;
+ }
+
+ if (updateElement != -1) {
+ PGraphics.showWarning("PShape3D: can load only one type of data at the time");
+ return;
+ }
+
+ if (PGraphicsOpenGL2.maxTextureUnits <= unit) {
+ PGraphics.showWarning("PShape3D: wrong texture unit");
+ return;
+ }
+
+ updateElement = TEXCOORDS;
+ firstUpdateIdx = first;
+ lastUpdateIdx = last;
+ updateTexunit = unit;
+
+ if (numTexBuffers <= unit) {
+ addTexBuffers(unit - numTexBuffers + 1);
+ }
+
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glTexCoordBufferID[unit]);
+ texCoordBuffer = gl.glMapBuffer(GL.GL_ARRAY_BUFFER, GL.GL_WRITE_ONLY).asFloatBuffer();
+
+ texcoords = allTexcoords[unit];
+ }
+
+
+ public void updateTexcoords() {
+ if (updateElement == TEXCOORDS) {
+ int offset = firstUpdateIdx * 2;
+ int size = (lastUpdateIdx - firstUpdateIdx + 1) * 2;
+
+ convertTexcoords();
+
+ texCoordBuffer.position(0);
+ texCoordBuffer.put(convTexcoords, offset, size);
+ texCoordBuffer.flip();
+
+ gl.glUnmapBuffer(GL.GL_ARRAY_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+
+ texCoordSet[updateTexunit] = true;
+
+ updateElement = -1;
+ } else {
+ PGraphics.showWarning("PShape3D: need to call loadTexcoords() first");
+ }
+ }
+
+
+ public float[] get(int idx) {
+ float[] v = null;
+ if (updateElement == VERTICES) {
+ v = new float[3];
+ PApplet.arrayCopy(vertices, 3 * idx, v, 0, 3);
+ } else if (updateElement == COLORS) {
+ v = new float[4];
+ PApplet.arrayCopy(colors, 4 * idx, v, 0, 4);
+ } else if (updateElement == NORMALS) {
+ v = new float[3];
+ PApplet.arrayCopy(normals, 3 * idx, v, 0, 3);
+ } else if (updateElement == TEXCOORDS) {
+ v = new float[2];
+ PApplet.arrayCopy(texcoords, 2 * idx, v, 0, 2);
+ }
+ return v;
+ }
+
+
+ public void set(int idx, float[] v) {
+ if (updateElement == VERTICES) {
+ PApplet.arrayCopy(v, 0, vertices, 3 * idx, 3);
+ } else if (updateElement == COLORS) {
+ PApplet.arrayCopy(v, 0, colors, 4 * idx, 4);
+ } else if (updateElement == NORMALS) {
+ PApplet.arrayCopy(v, 0, normals, 3 * idx, 3);
+ } else if (updateElement == TEXCOORDS) {
+ PApplet.arrayCopy(v, 0, texcoords, 2 * idx, 2);
+ }
+ }
+
+
+ public void set(int idx, int c) {
+ set(idx, rgba(c));
+ }
+
+
+ public void set(int idx, float x, float y) {
+ if (updateElement == VERTICES) {
+ set(idx, new float[] {x, y, 0});
+ } else if (updateElement == TEXCOORDS) {
+ set(idx, new float[] {x, y});
+ }
+ }
+
+
+ public void set(int idx, float x, float y, float z) {
+ if (updateElement == VERTICES) {
+ set(idx, new float[] {x, y, z});
+ } else if (updateElement == NORMALS) {
+ set(idx, new float[] {x, y, z});
+ } else if (updateElement == COLORS) {
+ set(idx, new float[] {x, y, z, 1});
+ }
+ }
+
+
+ public void set(int idx, float x, float y, float z, float w) {
+ if (updateElement == COLORS) {
+ set(idx, new float[] {x, y, z, w});
+ }
+ }
+
+
+ static public int color(float[] rgba) {
+ int r = (int)(rgba[0] * 255);
+ int g = (int)(rgba[1] * 255);
+ int b = (int)(rgba[2] * 255);
+ int a = (int)(rgba[3] * 255);
+
+ return a << 24 | r << 16 | g << 8 | b;
+ }
+
+
+ static public float[] rgba(int c) {
+ int a = (c >> 24) & 0xFF;
+ int r = (c >> 16) & 0xFF;
+ int g = (c >> 8) & 0xFF;
+ int b = (c >> 0) & 0xFF;
+
+ float[] res = new float[4];
+ res[0] = r / 255.0f;
+ res[1] = g / 255.0f;
+ res[2] = b / 255.0f;
+ res[3] = a / 255.0f;
+
+ return res;
+ }
+
+ public void autoBounds(boolean value) {
+ root.autoBounds = value;
+ }
+
+ public void updateBounds() {
+ updateBounds(firstVertex, lastVertex);
+ }
+
+ protected void updateBounds(int first, int last) {
+ if (first <= firstVertex && lastVertex <= last) {
+ resetBounds();
+ }
+
+ if (family == GROUP) {
+ if (root == this && childCount == 0) {
+ // This might happen: the vertices has been set
+ // first but children still not initialized. So we
+ // need to calculate the bounding box directly:
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ updateBounds(vertices[3 * i + 0], vertices[3 * i + 1], vertices[3 * i + 2]);
+ }
+ } else {
+ for (int i = 0; i < childCount; i++) {
+ PShape3D child = (PShape3D)children[i];
+ child.updateBounds(first, last);
+ xmin = PApplet.min(xmin, child.xmin);
+ xmax = PApplet.max(xmax, child.xmax);
+
+ ymin = PApplet.min(ymin, child.ymin);
+ ymax = PApplet.max(ymax, child.ymax);
+
+ zmin = PApplet.min(zmin, child.zmin);
+ zmax = PApplet.max(zmax, child.zmax);
+
+ width = xmax - xmin;
+ height = ymax - ymin;
+ depth = zmax - zmin;
+ }
+ }
+ } else {
+ // TODO: extract minimum and maximum values using some sorting algorithm (maybe).
+ int n0 = PApplet.max(first, firstVertex);
+ int n1 = PApplet.min(last, lastVertex);
+
+ for (int i = n0; i <= n1; i++) {
+ updateBounds(vertices[3 * i + 0], vertices[3 * i + 1], vertices[3 * i + 2]);
+ }
+ }
+ }
+
+ protected void resetBounds() {
+ if (family == GROUP) {
+ for (int i = 0; i < childCount; i++) {
+ ((PShape3D)children[i]).resetBounds();
+ }
+ }
+ width = height = depth = 0;
+ xmin = ymin = zmin = 10000;
+ xmax = ymax = zmax = -10000;
+ }
+
+ protected void updateBounds(float x, float y, float z) {
+ xmin = PApplet.min(xmin, x);
+ xmax = PApplet.max(xmax, x);
+
+ ymin = PApplet.min(ymin, y);
+ ymax = PApplet.max(ymax, y);
+
+ zmin = PApplet.min(zmin, z);
+ zmax = PApplet.max(zmax, z);
+
+ width = xmax - xmin;
+ height = ymax - ymin;
+ depth = zmax - zmin;
+ }
+
+
+ // Convert texture coordinates given by the user to values required by
+ // the GPU (non-necessarily normalized because of texture size being smaller
+ // than image size, for instance).
+ protected void convertTexcoords() {
+ PTexture tex;
+ float u, v;
+
+ PTexture tex0 = null;
+ float uscale = 1.0f;
+ float vscale = 1.0f;
+ float cx = 0.0f;
+ float sx = +1.0f;
+ float cy = 0.0f;
+ float sy = +1.0f;
+ for (int i = firstUpdateIdx; i <= lastUpdateIdx; i++) {
+ if (vertexChild[i] != null && vertexChild[i].textures[updateTexunit] != null) {
+ PImage img = vertexChild[i].textures[updateTexunit];
+ tex = pgl.getTexture(img);
+
+ if (tex != tex0) {
+ uscale = 1.0f;
+ vscale = 1.0f;
+ cx = 0.0f;
+ sx = +1.0f;
+ cy = 0.0f;
+ sy = +1.0f;
+
+ if (tex != null) {
+ if (tex.isFlippedX()) {
+ cx = 1.0f;
+ sx = -1.0f;
+ }
+
+ if (tex.isFlippedY()) {
+ cy = 1.0f;
+ sy = -1.0f;
+ }
+
+ uscale *= tex.getMaxTexCoordU();
+ vscale *= tex.getMaxTexCoordV();
+ }
+ tex0 = tex;
+ }
+
+ u = texcoords[2 * i + 0];
+ v = texcoords[2 * i + 1];
+
+ u = (cx + sx * u) * uscale;
+ v = (cy + sy * v) * vscale;
+
+ convTexcoords[2 * i + 0] = u;
+ convTexcoords[2 * i + 1] = v;
+ }
+ }
+ }
+
+ ////////////////////////////////////////////////////////////
+
+ // Child shapes
+
+
+ static public PShape createChild(int n0, int n1) {
+ return createChild(null, n0, n1, POINTS, 0, null);
+ }
+
+
+ static public PShape createChild(String name, int n0, int n1) {
+ return createChild(name, n0, n1, POINTS, 0, null);
+ }
+
+
+ static public PShape createChild(String name, int n0, int n1, int mode) {
+ return createChild(name, n0, n1, mode, 0, null);
+ }
+
+
+ static public PShape createChild(String name, int n0, int n1, int mode, float weight) {
+ return createChild(name, n0, n1, mode, weight, null);
+ }
+
+
+ static public PShape createChild(String name, int n0, int n1, int mode, float weight, PImage[] tex) {
+ PShape3D child = new PShape3D();
+ child.family = PShape.GEOMETRY;
+ child.name = name;
+ child.firstVertex = n0;
+ child.lastVertex = n1;
+ child.setDrawModeImpl(mode);
+ child.strokeWeight = weight;
+ child.textures = new PImage[PGraphicsOpenGL2.MAX_TEXTURES];
+ child.renderTextures = new PTexture[PGraphicsOpenGL2.MAX_TEXTURES];
+
+ java.util.Arrays.fill(child.textures, null);
+ if (tex != null) {
+ int n = PApplet.min(tex.length, child.textures.length);
+ PApplet.arrayCopy(tex, 0, child.textures, 0, n);
+ }
+ return child;
+ }
+
+
+ public void addChild(String name, int n0, int n1) {
+ PShape child = createChild(name, n0, n1, getDrawModeImpl());
+ addChild(child);
+ }
+
+
+ public void addChild(String name, int n0, int n1, int mode) {
+ PShape child = createChild(name, n0, n1, mode);
+ addChild(child);
+ }
+
+
+ public void addChild(String name, int n0, int n1, int mode, float weight) {
+ PShape child = createChild(name, n0, n1, mode, weight);
+ addChild(child);
+ }
+
+
+ public void addChild(String name, int n0, int n1, int mode, float weight, PImage[] tex) {
+ PShape child = createChild(name, n0, n1, mode, weight, tex);
+ addChild(child);
+ }
+
+
+ public void addChild(PShape who) {
+ addChildImpl(who, true);
+ }
+
+
+ protected void addChildImpl(PShape who, boolean newShape) {
+ if (family == GROUP) {
+ super.addChild(who);
+
+ if (newShape) {
+ PShape3D who3d = (PShape3D)who;
+ who3d.papplet = papplet;
+ who3d.pgl = pgl;
+ who3d.gl = gl;
+ who3d.root = root;
+
+ // So we can use the load/update methods in the child geometries.
+ who3d.numTexBuffers = root.numTexBuffers;
+
+ who3d.glVertexBufferID = root.glVertexBufferID;
+ who3d.glColorBufferID = root.glColorBufferID;
+ who3d.glNormalBufferID = root.glNormalBufferID;
+ who3d.glTexCoordBufferID = root.glTexCoordBufferID;
+
+ who3d.vertexBuffer = root.vertexBuffer;
+ who3d.colorBuffer = root.colorBuffer;
+ who3d.normalBuffer = root.normalBuffer;
+ who3d.texCoordBuffer = root.texCoordBuffer;
+
+ who3d.vertices = root.vertices;
+ who3d.colors = root.colors;
+ who3d.normals = root.normals;
+ who3d.texcoords = root.texcoords;
+
+ who3d.convTexcoords = root.convTexcoords;
+ who3d.allTexcoords = root.allTexcoords;
+ who3d.vertexChild = root.vertexChild;
+ who3d.texCoordSet = root.texCoordSet;
+
+ // In case the style was disabled from the root.
+ who3d.style = root.style;
+
+ // Updating vertex information.
+ for (int n = who3d.firstVertex; n <= who3d.lastVertex; n++) {
+ who3d.vertexChild[n] = who3d;
+ }
+
+ // If this new child shape has textures, then we should update the texture coordinates
+ // for the vertices involved. All we need to do is to call loadTexcoords and updateTexcoords
+ // so the current texture coordinates are converted into values that take into account
+ // the texture flipping, max UV ranges, etc.
+ for (int i = 0; i < who3d.textures.length; i++) {
+ if (who3d.textures[i] != null && who3d.texCoordSet[i]) {
+ who3d.loadTexcoords(i);
+ who3d.updateTexcoords();
+ }
+ }
+ }
+ } else {
+ PGraphics.showWarning("PShape3D: Child shapes can only be added to a group shape.");
+ }
+ }
+
+ /**
+ * Add a shape to the name lookup table.
+ */
+ public void addName(String nom, PShape shape) {
+ if (nameTable == null) {
+ nameTable = new HashMap();
+ }
+ nameTable.put(nom, shape);
+ }
+
+ protected void addDefaultChild() {
+ PShape child = createChild("geometry", 0, vertexCount - 1, getDrawModeImpl(), 0, null);
+ addChild(child);
+ }
+
+
+ public PShape groupChildren(int cidx0, int cidx1, String gname) {
+ if (family != GROUP) return null; // Can be done only in a GROUP shape.
+ return groupChildren(new int[] {cidx0, cidx1}, gname);
+ }
+
+
+ public PShape groupChildren(int cidx0, int cidx1, int cidx2, String gname) {
+ if (family != GROUP) return null; // Can be done only in a GROUP shape.
+ return groupChildren(new int[] {cidx0, cidx1, cidx2}, gname);
+ }
+
+
+ public PShape groupChildren(int[] cidxs, String gname) {
+ if (family != GROUP) return null; // Can be done only in a GROUP shape.
+ PShape[] temp = new PShape[cidxs.length];
+
+ int nsel = 0;
+ for (int i = 0; i < cidxs.length; i++) {
+ PShape child = getChild(cidxs[i]);
+ if (child != null) {
+ temp[nsel] = child;
+ nsel++;
+ }
+ }
+
+ PShape[] schildren = new PShape[nsel];
+ PApplet.arrayCopy(temp, schildren, nsel);
+
+ return groupChildren(schildren, gname);
+ }
+
+
+ public PShape groupChildren(String cname0, String cname1, String gname) {
+ if (family != GROUP) return null; // Can be done only in a GROUP shape.
+ return groupChildren(new String[] {cname0, cname1}, gname);
+ }
+
+
+ public PShape groupChildren(String cname0, String cname1, String cname2, String gname) {
+ if (family != GROUP) return null; // Can be done only in a GROUP shape.
+ return groupChildren(new String[] {cname0, cname1, cname2}, gname);
+ }
+
+
+ public PShape groupChildren(String[] cnames, String gname) {
+ if (family != GROUP) return null; // Can be done only in a GROUP shape.
+
+ PShape[] temp = new PShape[cnames.length];
+
+ int nsel = 0;
+ for (int i = 0; i < cnames.length; i++) {
+ PShape child = getChild(cnames[i]);
+ if (child != null) {
+ temp[nsel] = child;
+ nsel++;
+ }
+ }
+
+ PShape[] schildren = new PShape[nsel];
+ PApplet.arrayCopy(temp, schildren, nsel);
+
+ return groupChildren(schildren, gname);
+ }
+
+
+ public PShape groupChildren(PShape[] gchildren, String gname) {
+ if (family != GROUP) return null; // Can be done only in a GROUP shape.
+
+ // Creating the new group containing the children.
+ PShape3D group = new PShape3D();
+ group.family = PShape.GROUP;
+ group.name = gname;
+ group.papplet = papplet;
+ group.pgl = pgl;
+ group.gl = gl;
+ group.root = root;
+
+ PShape child, p;
+ int idx;
+
+ // Inserting the new group at the position of the first
+ // child shape in the group (in its original parent).
+ child = gchildren[0];
+ p = child.getParent();
+ if (p != null) {
+ idx = p.getChildIndex(child);
+ if (idx < 0) idx = 0;
+ } else {
+ p = this;
+ idx = 0;
+ }
+ p.addChild(group, idx);
+
+ // Removing the children in the new group from their
+ // respective parents.
+ for (int i = 0; i < gchildren.length; i++) {
+ child = gchildren[i];
+ p = child.getParent();
+ if (p != null) {
+ idx = p.getChildIndex(child);
+ if (-1 < idx) {
+ p.removeChild(idx);
+ }
+ }
+ }
+
+ group.firstVertex = root.vertexCount;
+ group.lastVertex = 0;
+ for (int i = 0; i < gchildren.length; i++) {
+ group.firstVertex = PApplet.min(group.firstVertex, ((PShape3D)gchildren[i]).firstVertex);
+ group.lastVertex = PApplet.max(group.lastVertex, ((PShape3D)gchildren[i]).lastVertex);
+ }
+
+ // Adding the children shapes to the new group.
+ for (int i = 0; i < gchildren.length; i++) {
+ group.addChildImpl(gchildren[i], false);
+ }
+
+ return group;
+ }
+
+
+ public int getFirstVertex() {
+ return firstVertex;
+ }
+
+
+ public int getFirstVertex(int idx) {
+ if (0 <= idx && idx < childCount) {
+ return ((PShape3D)children[idx]).getFirstVertex();
+ }
+ return -1;
+ }
+
+
+ public void setFirstVertex(int n0) {
+ firstVertex = n0;
+ }
+
+
+ public void setFirstVertex(int idx, int n0) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setFirstVertex(n0);
+ }
+ }
+
+
+ public int getLastVertex() {
+ return lastVertex;
+ }
+
+
+ public int getLastVertex(int idx) {
+ if (0 <= idx && idx < childCount) {
+ return ((PShape3D)children[idx]).getLastVertex();
+ }
+ return -1;
+ }
+
+
+ public void setLastVertex(int n1) {
+ lastVertex = n1;
+ }
+
+
+ public void setLastVertex(int idx, int n1) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setLastVertex(n1);
+ }
+ }
+
+
+ public void setDrawMode(int mode) {
+ if (family == GROUP) {
+ init();
+ setDrawModeImpl(mode);
+ for (int n = 0; n < childCount; n++) {
+ setDrawMode(n, mode);
+ }
+ } else {
+ setDrawModeImpl(mode);
+ }
+ }
+
+
+ public void setDrawMode(int idx, int mode) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setDrawMode(mode);
+ }
+ }
+
+
+ protected void setDrawModeImpl(int mode) {
+ pointSprites = false;
+ if (mode == POINTS) glMode = GL.GL_POINTS;
+ else if (mode == POINT_SPRITES) {
+ glMode = GL.GL_POINTS;
+ pointSprites = true;
+ }
+ else if (mode == LINES) glMode = GL.GL_LINES;
+ else if (mode == LINE_STRIP) glMode = GL.GL_LINE_STRIP;
+ else if (mode == LINE_LOOP) glMode = GL.GL_LINE_LOOP;
+ else if (mode == TRIANGLES) glMode = GL.GL_TRIANGLES;
+ else if (mode == TRIANGLE_FAN) glMode = GL.GL_TRIANGLE_FAN;
+ else if (mode == TRIANGLE_STRIP) glMode = GL.GL_TRIANGLE_STRIP;
+ else {
+ throw new RuntimeException("PShape3D: Unknown draw mode");
+ }
+ }
+
+
+ protected boolean isTexturable() {
+ return glMode == GL.GL_TRIANGLES ||
+ glMode == GL.GL_TRIANGLE_FAN ||
+ glMode == GL.GL_TRIANGLE_STRIP ||
+ pointSprites;
+ }
+
+
+ public int getDrawMode() {
+ if (family == GROUP) {
+ init();
+ return getDrawMode(0);
+ } else {
+ return getDrawModeImpl();
+ }
+ }
+
+
+ public int getDrawMode(int idx) {
+ if (0 <= idx && idx < childCount) {
+ return ((PShape3D)children[idx]).getDrawMode();
+ }
+ return -1;
+ }
+
+
+ protected int getDrawModeImpl() {
+ if (glMode == GL.GL_POINTS) {
+ if (pointSprites) return POINT_SPRITES;
+ else return POINTS;
+ } else if (glMode == GL.GL_LINES) return LINES;
+ else if (glMode == GL.GL_LINE_STRIP) return LINE_STRIP;
+ else if (glMode == GL.GL_LINE_LOOP) return LINE_LOOP;
+ else if (glMode == GL.GL_TRIANGLES) return TRIANGLES;
+ else if (glMode == GL.GL_TRIANGLE_FAN) return TRIANGLE_FAN;
+ else if (glMode == GL.GL_TRIANGLE_STRIP) return TRIANGLE_STRIP;
+ else return -1;
+ }
+
+
+ public void setTexture(PImage tex) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setTexture(i, tex);
+ }
+ } else {
+ setTextureImpl(tex, 0);
+ }
+ }
+
+
+ public void setTexture(PImage tex0, PImage tex1) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setTexture(i, tex0, tex1);
+ }
+ } else {
+ setTextureImpl(tex0, 0);
+ setTextureImpl(tex1, 1);
+ }
+ }
+
+
+ public void setTexture(PImage tex0, PImage tex1, PImage tex2) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setTexture(i, tex0, tex1, tex2);
+ }
+ } else {
+ setTextureImpl(tex0, 0);
+ setTextureImpl(tex1, 1);
+ setTextureImpl(tex2, 2);
+ }
+ }
+
+
+ public void setTexture(PImage tex0, PImage tex1, PImage tex2, PImage tex3) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setTexture(i, tex0, tex1, tex2, tex3);
+ }
+ } else {
+ setTextureImpl(tex0, 0);
+ setTextureImpl(tex1, 1);
+ setTextureImpl(tex2, 2);
+ setTextureImpl(tex3, 3);
+ }
+ }
+
+
+ public void setTexture(PImage[] tex) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setTexture(i, tex);
+ }
+ } else {
+ for (int i = 0; i < tex.length; i++) {
+ setTextureImpl(tex[i], i);
+ }
+ }
+ }
+
+
+ public void setTexture(int idx, PImage tex) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setTexture(tex);
+ }
+ }
+
+
+ public void setTexture(int idx, PImage tex0, PImage tex1) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setTexture(tex0, tex1);
+ }
+ }
+
+
+ public void setTexture(int idx, PImage tex0, PImage tex1, PImage tex2) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setTexture(tex0, tex1, tex2);
+ }
+ }
+
+
+ public void setTexture(int idx, PImage tex0, PImage tex1, PImage tex2, PImage tex3) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setTexture(tex0, tex1, tex2, tex3);
+ }
+ }
+
+
+ public void setTexture(int idx, PImage[] tex) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setTexture(tex);
+ }
+ }
+
+
+ protected void setTextureImpl(PImage tex, int unit) {
+ if (unit < 0 || PGraphicsOpenGL2.maxTextureUnits <= unit) {
+ System.err.println("PShape3D: Wrong texture unit.");
+ return;
+ }
+
+ if (numTexBuffers <= unit) {
+ root.addTexBuffers(unit - numTexBuffers + 1);
+ }
+
+ if (tex == null) {
+ throw new RuntimeException("PShape3D: trying to set null texture.");
+ }
+
+ if (texCoordSet[unit] && isTexturable()) {
+ // Ok, setting a new texture, when texture coordinates have already been set.
+ // What is the problem? the new texture might have different max UV coords,
+ // flippedX/Y values, so the texture coordinates need to be updated accordingly...
+
+ // The way to do it is just load the texcoords array (in the parent)...
+ loadTexcoords(unit);
+ // ... then replacing the old texture with the new and...
+ textures[unit] = tex;
+ // ...,finally, updating the texture coordinates, step in which the texcoords
+ // array is converted, this time using the new texture.
+ updateTexcoords();
+ } else {
+ textures[unit] = tex;
+ }
+ }
+
+
+ public PImage[] getTexture() {
+ if (family == GROUP) {
+ init();
+ return getTexture(0);
+ } else {
+ return textures;
+ }
+ }
+
+
+ public PImage[] getTexture(int idx) {
+ if (0 <= idx && idx < childCount) {
+ return ((PShape3D)children[idx]).getTexture();
+ }
+ return null;
+ }
+
+
+ public float getStrokeWeight() {
+ if (family == GROUP) {
+ init();
+ return getStrokeWeight(0);
+ } else {
+ return strokeWeight;
+ }
+ }
+
+
+ public float getStrokeWeight(int idx) {
+ if (0 <= idx && idx < childCount) {
+ return ((PShape3D)children[idx]).getStrokeWeight();
+ }
+ return 0;
+ }
+
+
+ public void setStrokeWeight(float sw) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setStrokeWeight(i, sw);
+ }
+ } else {
+ strokeWeight = sw;
+ }
+ }
+
+
+ public void setStrokeWeight(int idx, float sw) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setStrokeWeight(sw);
+ }
+ }
+
+
+ public float getMaxSpriteSize() {
+ if (family == GROUP) {
+ init();
+ return getMaxSpriteSize(0);
+ } else {
+ return maxSpriteSize;
+ }
+ }
+
+
+ public float getMaxSpriteSize(int idx) {
+ if (0 <= idx && idx < childCount) {
+ return ((PShape3D)children[idx]).getMaxSpriteSize();
+ }
+ return 0;
+ }
+
+
+ public void setMaxSpriteSize(float s) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setMaxSpriteSize(i, s);
+ }
+ } else {
+ setMaxSpriteSizeImpl(s);
+ }
+ }
+
+
+ public void setMaxSpriteSize(int idx, float s) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setMaxSpriteSize(s);
+ }
+ }
+
+
+ protected void setMaxSpriteSizeImpl(float s) {
+ maxSpriteSize = PApplet.min(s, PGraphicsOpenGL2.maxPointSize);
+ }
+
+
+ public void setSpriteSize(float s) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setSpriteSize(i, s);
+ }
+ } else {
+ setSpriteSizeImpl(s);
+ }
+ }
+
+
+ public void setSpriteSize(float s, float d, int mode) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setSpriteSize(i, s, d, mode);
+ }
+ } else {
+ setSpriteSizeImpl(s, d, mode);
+ }
+ }
+
+
+ public void setSpriteSize(int idx, float s) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setSpriteSize(s);
+ }
+ }
+
+
+ public void setSpriteSize(int idx, float s, float d, int mode) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setSpriteSize(s, d, mode);
+ }
+ }
+
+
+ // Sets the coefficient of the distance attenuation function so that the
+ // size of the sprite is exactly s when its distance from the camera is d.
+ protected void setSpriteSizeImpl(float s, float d, int mode) {
+ float s0 = maxSpriteSize;
+ if (mode == LINEAR) {
+ spriteDistAtt[1] = (s0 - s) / (d * s);
+ spriteDistAtt[2] = 0;
+ } else if (mode == QUADRATIC) {
+ spriteDistAtt[1] = 0;
+ spriteDistAtt[2] = (s0 - s) / (d * d * s);
+ } else {
+ PGraphics.showWarning("Invalid point sprite mode");
+ }
+ }
+
+
+ // Sets constant sprite size equal to s.
+ protected void setSpriteSizeImpl(float s) {
+ setMaxSpriteSizeImpl(s);
+ spriteDistAtt[1] = 0;
+ spriteDistAtt[2] = 0;
+ }
+
+
+ public void setColor(int c) {
+ setColor(rgba(c));
+ }
+
+
+ public void setColor(float r, float g, float b, float a) {
+ setColor(new float[] {r, g, b, a});
+ }
+
+
+ public void setColor(float[] c) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setColor(i, c);
+ }
+ } else {
+ setColorImpl(c);
+ }
+ }
+
+
+ public void setColor(int idx, int c) {
+ setColor(idx, rgba(c));
+ }
+
+
+ public void setColor(int idx, float r, float g, float b, float a) {
+ setColor(idx, new float[] {r, g, b, a});
+ }
+
+
+ public void setColor(int idx, float[] c) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setColor(c);
+ }
+ }
+
+
+ protected void setColorImpl(float[] c) {
+ PShape3D p = (PShape3D)root;
+ p.loadColors();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ p.set(i, c);
+ }
+ p.updateColors();
+ }
+
+
+ public void setNormal(float nx, float ny, float nz) {
+ setNormal(new float[] {nx, ny, nz});
+ }
+
+
+ public void setNormal(float[] n) {
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ setNormal(i, n);
+ }
+ } else {
+ setNormalImpl(n);
+ }
+ }
+
+
+ public void setNormal(int idx, float nx, float ny, float nz) {
+ setNormal(idx, new float[] {nx, ny, nz});
+ }
+
+
+ public void setNormal(int idx, float[] n) {
+ if (0 <= idx && idx < childCount) {
+ ((PShape3D)children[idx]).setNormal(n);
+ }
+ }
+
+
+ protected void setNormalImpl(float[] n) {
+ PShape3D p = (PShape3D)root;
+ p.loadNormals();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ p.set(i, n);
+ }
+ p.updateNormals();
+ }
+
+ // Optimizes the array list containing children shapes so that shapes with identical
+ // parameters are removed. Also, making sure that the names are unique.
+ protected void optimizeChildren(ArrayList childList) {
+ PShape3D child0, child1;
+
+ // Expanding identical, contiguous shapes. Names are taken into account (two
+ // shapes with different names are considered to be different, even though the
+ // rest of their parameters are identical).
+ child0 = (PShape3D)childList.get(0);
+ for (int i = 1; i < childList.size(); i++) {
+ child1 = (PShape3D)childList.get(i);
+ if (child0.equalTo(child1, false)) {
+ child0.lastVertex = child1.lastVertex; // Extending child0.
+ // Updating the vertex data:
+ for (int n = child0.firstVertex; n <= child0.lastVertex; n++) {
+ vertexChild[n] = child0;
+ }
+ child1.firstVertex = child1.lastVertex = -1; // Marking for deletion.
+ } else {
+ child0 = child1;
+ }
+ }
+
+ // Deleting superfluous shapes.
+ for (int i = childList.size() - 1; i >= 0; i--) {
+ if (((PShape3D)childList.get(i)).lastVertex == -1) {
+ childList.remove(i);
+ }
+ }
+
+ // Making sure the names are unique.
+ for (int i = 1; i < childList.size(); i++) {
+ child1 = (PShape3D)childList.get(i);
+ for (int j = i - 1; j >= 0; j--) {
+ child0 = (PShape3D)childList.get(j);
+ if (child1.name.equals(child0.name)) {
+ int pos = child0.name.indexOf(':');
+ if (-1 < pos) {
+ // The name of the preceding child contains the ':'
+ // character so trying to use the following substring
+ // as a number.
+ String nstr = child0.name.substring(pos + 1);
+ int n = 1;
+ try {
+ n = Integer.parseInt(nstr);
+ } catch (NumberFormatException e) {
+ child0.name += ":1";
+ }
+ child1.name += ":" + (n + 1);
+ } else {
+ child0.name += ":1";
+ child1.name += ":2";
+ }
+ }
+ }
+ }
+ }
+
+
+ protected boolean equalTo(PShape3D child, boolean ignoreNames) {
+ boolean res = family == child.family &&
+ glMode == child.glMode &&
+ strokeWeight == child.strokeWeight &&
+ (ignoreNames || name.equals(child.name));
+ if (!res) return false;
+ for (int i = 0; i < textures.length; i++) {
+ if (textures[i] != child.textures[i]) {
+ res = false;
+ }
+ }
+ return res;
+ }
+
+ ////////////////////////////////////////////////////////////
+
+
+ // Some overloading of translate, rotate, scale
+
+ public void resetMatrix() {
+ checkMatrix(3);
+ matrix.reset();
+ }
+
+ public void translate(float tx, float ty) {
+ checkMatrix(3);
+ matrix.translate(tx, ty, 0);
+ }
+
+ public void rotate(float angle) {
+ checkMatrix(3);
+ matrix.rotate(angle);
+ }
+
+ public void scale(float s) {
+ checkMatrix(3);
+ matrix.scale(s);
+ }
+
+ public void scale(float x, float y) {
+ checkMatrix(3);
+ matrix.scale(x, y);
+ }
+
+ public void centerAt(float cx, float cy, float cz) {
+ float dx = cx - 0.5f * (xmin + xmax);
+ float dy = cy - 0.5f * (ymin + ymax);
+ float dz = cz - 0.5f * (zmin + zmax);
+ // Centering
+ loadVertices();
+ for (int i = 0; i < vertexCount; i++) {
+ vertices[3 * i + 0] += dx;
+ vertices[3 * i + 1] += dy;
+ vertices[3 * i + 2] += dz;
+ }
+ updateVertices();
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Bulk vertex operations.
+
+ public void setVertices(ArrayList vertexList) {
+ setVertices(vertexList, 0);
+ }
+
+ public void setVertices(ArrayList vertexList, int offset) {
+ loadVertices();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ PVector v = (PVector)vertexList.get(i - firstVertex + offset);
+ set(i, v.x, v.y, v.z);
+ }
+ updateVertices();
+ }
+
+ public void setColors(ArrayList colorList) {
+ setColors(colorList, 0);
+ }
+
+ public void setColors(ArrayList colorList, int offset) {
+ loadColors();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ float[] c = (float[])colorList.get(i - firstVertex + offset);
+ set(i, c);
+ }
+ updateColors();
+ }
+
+
+ public void setNormals(ArrayList normalList) {
+ setNormals(normalList, 0);
+ }
+
+ public void setNormals(ArrayList normalList, int offset) {
+ loadNormals();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ PVector n = (PVector)normalList.get(i - firstVertex + offset);
+ set(i, n.x, n.y, n.z);
+ }
+ updateNormals();
+ }
+
+
+ public void setTexcoords(ArrayList tcoordList) {
+ setTexcoords(tcoordList, 0);
+ }
+
+ public void setTexcoords(ArrayList tcoordList, int offset) {
+ setTexcoords(0, tcoordList, offset);
+ }
+
+ public void setTexcoords(int unit, ArrayList tcoordList) {
+ setTexcoords(unit, tcoordList, 0);
+ }
+
+ public void setTexcoords(int unit, ArrayList tcoordList, int offset) {
+ loadTexcoords(unit);
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ PVector tc = (PVector)tcoordList.get(i - firstVertex + offset);
+ set(i, tc.x, tc.y);
+ }
+ updateTexcoords();
+ }
+
+
+ public void setChildren(ArrayList who) {
+ if (family != GROUP) return; // Can be done only from a group shape.
+
+ childCount = 0;
+ for (int i = 0; i < who.size(); i++) {
+ PShape child = (PShape)who.get(i);
+ addChild(child);
+ }
+ }
+
+ public void mergeChildren() {
+ if (family != GROUP) return; // Can be done only from a group shape.
+
+ if (children == null) {
+ if (root == this) {
+ addDefaultChild();
+ } else {
+ return;
+ }
+ } else {
+ PShape3D child0, child1;
+
+ // Expanding identical, contiguous shapes (names are ignored).
+ int ndiff = 1;
+ // Looking for the first geometry child.
+ int i0 = 0;
+ child0 = null;
+ for (int i = 0; i < childCount; i++) {
+ child0 = (PShape3D)children[i];
+ if (child0.family == GROUP) {
+ child0.mergeChildren();
+ } else {
+ i0 = i + 1;
+ break;
+ }
+ }
+ if (i0 == 0) return; // No geometry child found.
+ for (int i = i0; i < childCount; i++) {
+ child1 = (PShape3D)children[i];
+ if (child1.family == GROUP) {
+ child1.mergeChildren();
+ continue;
+ }
+ if (child0.equalTo(child1, true)) {
+ child0.lastVertex = child1.lastVertex; // Extending child0.
+ // Updating the vertex data:
+ for (int n = child0.firstVertex; n <= child0.lastVertex; n++) {
+ vertexChild[n] = child0;
+ }
+ child1.firstVertex = child1.lastVertex = -1; // Marking for deletion.
+ } else {
+ child0 = child1;
+ ndiff++;
+ }
+ }
+
+ // Deleting superfluous shapes.
+ PShape[] temp = new PShape[ndiff];
+ int n = 0;
+ for (int i = 0; i < childCount; i++) {
+ child1 = (PShape3D)children[i];
+ if (child1.family == GEOMETRY && child1.lastVertex == -1 &&
+ child1.getName() != null && nameTable != null) {
+ nameTable.remove(child1.getName());
+ } else {
+ temp[n++] = child1;
+ }
+ }
+ children = temp;
+ childCount = ndiff;
+ }
+ }
+
+ public void translateVertices(float tx, float ty) {
+ translateVertices(tx, ty, 0);
+ }
+
+
+ public void translateVertices(float tx, float ty, float tz) {
+ init();
+ loadVertices();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ vertices[3 * i + 0] += tx;
+ vertices[3 * i + 1] += -ty;
+ vertices[3 * i + 2] += tz;
+ }
+ updateVertices();
+ }
+
+
+ public void rotateVerticesX(float angle) {
+ rotateVertices(angle, 1, 0, 0);
+ }
+
+
+ public void rotateVerticesY(float angle) {
+ rotateVertices(angle, 0, 1, 0);
+ }
+
+
+ public void rotateVerticesZ(float angle) {
+ rotateVertices(angle, 0, 0, 1);
+ }
+
+
+ public void rotateVertices(float angle) {
+ rotateVertices(angle, 0, 0, 1);
+ }
+
+
+ public void rotateVertices(float angle, float v0, float v1, float v2) {
+ init();
+
+ float norm2 = v0 * v0 + v1 * v1 + v2 * v2;
+ if (Math.abs(norm2 - 1) > EPSILON) {
+ // Normalizing rotation axis vector.
+ float norm = PApplet.sqrt(norm2);
+ v0 /= norm;
+ v1 /= norm;
+ v2 /= norm;
+ }
+
+ // Rotating around the center of the shape.
+ float cx = 0.5f * (xmin + xmax);
+ float cy = 0.5f * (ymin + ymax);
+ float cz = 0.5f * (zmin + zmax);
+
+ // Rotation matrix
+ float c = PApplet.cos(angle);
+ float s = PApplet.sin(angle);
+ float t = 1.0f - c;
+ float[] m = new float[9];
+ m[0] = (t*v0*v0) + c; // 0, 0
+ m[1] = (t*v0*v1) - (s*v2); // 0, 1
+ m[2] = (t*v0*v2) + (s*v1); // 0, 2
+ m[3] = (t*v0*v1) + (s*v2); // 1, 0
+ m[4] = (t*v1*v1) + c; // 1, 1
+ m[5] = (t*v1*v2) - (s*v0); // 1, 2
+ m[6] = (t*v0*v2) - (s*v1); // 2, 0
+ m[7] = (t*v1*v2) + (s*v0); // 2, 1
+ m[8] = (t*v2*v2) + c; // 2, 2
+
+ float x, y, z;
+
+ loadVertices();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ x = vertices[3 * i + 0] - cx;
+ y = vertices[3 * i + 1] - cy;
+ z = vertices[3 * i + 2] - cz;
+
+ vertices[3 * i + 0] = m[0] * x + m[1] * y + m[2] * z + cx;
+ vertices[3 * i + 1] = m[3] * x + m[4] * y + m[5] * z + cy;
+ vertices[3 * i + 2] = m[6] * x + m[7] * y + m[8] * z + cz;
+ }
+ updateVertices();
+
+ // Re-centering, because of loss of precision when applying the
+ // rotation matrix, the center of rotation moves slightly so
+ // after many consecutive rotations the object might translate
+ // significantly.
+ centerAt(cx, cy, cz);
+
+ // The normals also need to be rotated to reflect the new orientation
+ //of the faces.
+ loadNormals();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ x = normals[3 * i + 0];
+ y = normals[3 * i + 1];
+ z = normals[3 * i + 2];
+
+ normals[3 * i + 0] = m[0] * x + m[1] * y + m[2] * z + cx;
+ normals[3 * i + 1] = m[3] * x + m[4] * y + m[5] * z + cy;
+ normals[3 * i + 2] = m[6] * x + m[7] * y + m[8] * z + cz;
+ }
+ updateNormals();
+ }
+
+
+ public void scaleVertices(float s) {
+ scaleVertices(s, s, s);
+ }
+
+
+ public void scaleVertices(float sx, float sy) {
+ scaleVertices(sx, sy, 1);
+ }
+
+
+ public void scaleVertices(float x, float y, float z) {
+ init();
+ loadVertices();
+ for (int i = firstVertex; i <= lastVertex; i++) {
+ vertices[3 * i + 0] *= x;
+ vertices[3 * i + 1] *= y;
+ vertices[3 * i + 2] *= z;
+ }
+ updateVertices();
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Parameters
+
+ public Parameters getParameters() {
+ if (root != this) return null; // Can be done only from the root shape.
+
+ Parameters res = new Parameters();
+
+ res.drawMode = getDrawModeImpl();
+
+ if (glUsage == GL.GL_STATIC_DRAW) res.updateMode = STATIC;
+ else if (glUsage == GL.GL_DYNAMIC_DRAW) res.updateMode = DYNAMIC;
+ else if (glUsage == GL2.GL_STREAM_COPY) res.updateMode = STREAM;
+
+ return res;
+ }
+
+
+ protected void setParameters(Parameters params) {
+ if (root != this) return; // Can be done only from the root shape.
+
+ setDrawModeImpl(params.drawMode);
+
+ if (params.updateMode == STATIC) glUsage = GL.GL_STATIC_DRAW;
+ else if (params.updateMode == DYNAMIC) glUsage = GL.GL_DYNAMIC_DRAW;
+ else if (params.updateMode == STREAM) glUsage = GL2.GL_STREAM_COPY;
+ else {
+ throw new RuntimeException("PShape3D: Unknown update mode");
+ }
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Data allocation, deletion.
+
+ public void init() {
+ if (root != this) return; // Can be done only from the root shape.
+
+ if (readFromOBJ) {
+ recordOBJ();
+ centerAt(0, 0, 0);
+ }
+ if (children == null) {
+ addDefaultChild();
+ }
+ }
+
+
+ protected void initShape(int numVert) {
+ initShape(numVert, new Parameters());
+ }
+
+
+ protected void initShape(int numVert, Parameters params) {
+ // Checking we have what we need:
+ gl = pgl.gl2f;
+ if (gl == null) {
+ throw new RuntimeException("PShape3D: OpenGL ES 1.1 required");
+ }
+ if (!PGraphicsOpenGL2.vboSupported) {
+ throw new RuntimeException("PShape3D: Vertex Buffer Objects are not available");
+ }
+
+ setParameters(params);
+ allocateShape(numVert);
+ updateElement = -1;
+
+ resetBounds();
+ }
+
+
+ protected void initShapeOBJ(String filename, Parameters params) {
+ // Checking we have all we need:
+ gl = pgl.gl2f;
+ if (gl == null) {
+ throw new RuntimeException("PShape3D: OpenGL ES 1.1 required");
+ }
+ if (!PGraphicsOpenGL2.vboSupported) {
+ throw new RuntimeException("PShape3D: Vertex Buffer Objects are not available");
+ }
+
+ readFromOBJ = true;
+ objVertices = new ArrayList();
+ objNormal = new ArrayList();
+ objTexCoords = new ArrayList();
+ objFaces = new ArrayList();
+ objMaterials = new ArrayList();
+ BufferedReader reader = getBufferedReader(filename);
+ if (reader == null) {
+ throw new RuntimeException("PShape3D: Cannot read source file");
+ }
+
+ // Setting parameters.
+ if (params == null) {
+ params = PShape3D.newParameters(TRIANGLES, STATIC);
+ } else {
+ params.drawMode = TRIANGLES;
+ }
+ setParameters(params);
+
+ parseOBJ(reader, objVertices, objNormal, objTexCoords, objFaces, objMaterials);
+
+ // Putting the number of vertices retrieved from the OBJ file in the vertex count
+ // field, although the actual geometry hasn't been sent to the VBOs yet.
+ vertexCount = objVertices.size();
+ }
+
+ protected void allocateShape(int numVert) {
+ vertexCount = numVert;
+ numTexBuffers = 1;
+ firstVertex = 0;
+ lastVertex = numVert - 1;
+
+ initVertexData();
+ createVertexBuffer();
+ initColorData();
+ createColorBuffer();
+ initNormalData();
+ createNormalBuffer();
+ initTexCoordData();
+ createTexCoordBuffer();
+
+ initChildrenData();
+ }
+
+ protected void initChildrenData() {
+ children = null;
+ vertexChild = new PShape3D[vertexCount];
+ }
+
+ protected void initVertexData() {
+ ByteBuffer vbb = ByteBuffer.allocateDirect(vertexCount * 3 * PGraphicsOpenGL2.SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ vertexBuffer = vbb.asFloatBuffer();
+
+ vertices = new float[vertexCount * 3];
+ vertexBuffer.put(vertices);
+ vertexBuffer.flip();
+ }
+
+
+ protected void createVertexBuffer() {
+ deleteVertexBuffer(); // Just in the case this object is being re-initialized.
+
+ glVertexBufferID = pgl.createGLResource(PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glVertexBufferID);
+ final int bufferSize = vertexBuffer.capacity() * PGraphicsOpenGL2.SIZEOF_FLOAT;
+ gl.glBufferData(GL.GL_ARRAY_BUFFER, bufferSize, vertexBuffer, glUsage);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+ }
+
+
+ protected void initColorData() {
+ ByteBuffer vbb = ByteBuffer.allocateDirect(vertexCount * 4 * PGraphicsOpenGL2.SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ colorBuffer = vbb.asFloatBuffer();
+
+ colors = new float[vertexCount * 4];
+ // Set the initial color of all vertices to white, so they are initially visible
+ // even if the user doesn't set any vertex color.
+ Arrays.fill(colors, 1.0f);
+
+ colorBuffer.put(colors);
+ colorBuffer.flip();
+ }
+
+
+ protected void createColorBuffer() {
+ deleteColorBuffer();
+
+ glColorBufferID = pgl.createGLResource(PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glColorBufferID);
+ final int bufferSize = colorBuffer.capacity() * PGraphicsOpenGL2.SIZEOF_FLOAT;
+ gl.glBufferData(GL.GL_ARRAY_BUFFER, bufferSize, colorBuffer, glUsage);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+ }
+
+
+ protected void initNormalData() {
+ ByteBuffer vbb = ByteBuffer.allocateDirect(vertexCount * 3 * PGraphicsOpenGL2.SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ normalBuffer = vbb.asFloatBuffer();
+
+ normals = new float[vertexCount * 3];
+ normalBuffer.put(normals);
+ normalBuffer.flip();
+ }
+
+
+ protected void createNormalBuffer() {
+ deleteNormalBuffer();
+
+ glNormalBufferID = pgl.createGLResource(PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glNormalBufferID);
+ final int bufferSize = normalBuffer.capacity() * PGraphicsOpenGL2.SIZEOF_FLOAT;
+ gl.glBufferData(GL.GL_ARRAY_BUFFER, bufferSize, normalBuffer, glUsage);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+ }
+
+
+ protected void initTexCoordData() {
+ ByteBuffer vbb = ByteBuffer.allocateDirect(vertexCount * 2 * PGraphicsOpenGL2.SIZEOF_FLOAT);
+ vbb.order(ByteOrder.nativeOrder());
+ texCoordBuffer = vbb.asFloatBuffer();
+
+ allTexcoords = new float[1][vertexCount * 2];
+ texcoords = allTexcoords[0];
+ convTexcoords = new float[vertexCount * 2];
+ texCoordBuffer.put(convTexcoords);
+ texCoordBuffer.flip();
+
+ texCoordSet = new boolean[PGraphicsOpenGL2.MAX_TEXTURES];
+ }
+
+
+ protected void createTexCoordBuffer() {
+ if (glTexCoordBufferID == null) {
+ glTexCoordBufferID = new int[PGraphicsOpenGL2.MAX_TEXTURES];
+ java.util.Arrays.fill(glTexCoordBufferID, 0);
+ } else {
+ deleteTexCoordBuffer();
+ }
+
+ glTexCoordBufferID[0] = pgl.createGLResource(PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glTexCoordBufferID[0]);
+ final int bufferSize = texCoordBuffer.capacity() * PGraphicsOpenGL2.SIZEOF_FLOAT;
+ gl.glBufferData(GL.GL_ARRAY_BUFFER, bufferSize, texCoordBuffer, glUsage);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+ }
+
+
+ protected void addTexBuffers(int more) {
+ for (int i = 0; i < more; i++) {
+ int t = numTexBuffers + i;
+ deleteTexCoordBuffer(t);
+
+ glTexCoordBufferID[t] = pgl.createGLResource(PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glTexCoordBufferID[t]);
+ final int bufferSize = texCoordBuffer.capacity() * PGraphicsOpenGL2.SIZEOF_FLOAT;
+ gl.glBufferData(GL.GL_ARRAY_BUFFER, bufferSize, texCoordBuffer, glUsage);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+ }
+
+ // We need more arrays for texture coordinates, and to save the contents of the already
+ // existing ones.
+ float temp[][] = new float[numTexBuffers + more][vertexCount * 2];
+ for (int i = 0; i < numTexBuffers; i++) {
+ PApplet.arrayCopy(allTexcoords[i], temp[i]);
+ }
+ allTexcoords = temp;
+ texcoords = allTexcoords[0];
+
+ numTexBuffers += more;
+
+ // Updating the allTexcoords and numTexBuffers in all
+ // child geometries.
+ updateTexBuffers();
+ }
+
+ protected void updateTexBuffers() {
+ if (family == GROUP) {
+ for (int i = 0; i < childCount; i++) {
+ ((PShape3D)children[i]).updateTexBuffers();
+ }
+ } else {
+ numTexBuffers = root.numTexBuffers;
+ allTexcoords = root.allTexcoords;
+ texcoords = allTexcoords[0];
+ }
+ }
+
+ protected void deleteVertexBuffer() {
+ if (glVertexBufferID != 0) {
+ pgl.deleteGLResource(glVertexBufferID, PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ glVertexBufferID = 0;
+ }
+ }
+
+
+ protected void deleteColorBuffer() {
+ if (glColorBufferID != 0) {
+ pgl.deleteGLResource(glColorBufferID, PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ glColorBufferID = 0;
+ }
+ }
+
+
+ protected void deleteNormalBuffer() {
+ if (glNormalBufferID != 0) {
+ pgl.deleteGLResource(glNormalBufferID, PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ glNormalBufferID = 0;
+ }
+ }
+
+
+ protected void deleteTexCoordBuffer() {
+ for (int i = 0; i < numTexBuffers; i++) {
+ deleteTexCoordBuffer(i);
+ }
+ }
+
+
+ protected void deleteTexCoordBuffer(int idx) {
+ if (glTexCoordBufferID[idx] != 0) {
+ pgl.deleteGLResource(glTexCoordBufferID[idx], PGraphicsOpenGL2.GL_VERTEX_BUFFER);
+ glTexCoordBufferID[idx] = 0;
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////
+
+ // These methods are not available in PShape3D.
+
+
+ public float[] getVertex(int index) {
+ PGraphics.showMethodWarning("getVertex");
+ return null;
+ }
+
+
+ public float getVertexX(int index) {
+ PGraphics.showMethodWarning("getVertexX");
+ return 0;
+ }
+
+
+ public float getVertexY(int index) {
+ PGraphics.showMethodWarning("getVertexY");
+ return 0;
+ }
+
+
+ public float getVertexZ(int index) {
+ PGraphics.showMethodWarning("getVertexZ");
+ return 0;
+ }
+
+
+ public int[] getVertexCodes() {
+ PGraphics.showMethodWarning("getVertexCodes");
+ return null;
+ }
+
+
+ public int getVertexCodeCount() {
+ PGraphics.showMethodWarning("getVertexCodeCount");
+ return 0;
+ }
+
+
+ public int getVertexCode(int index) {
+ PGraphics.showMethodWarning("getVertexCode");
+ return 0;
+ }
+
+ ///////////////////////////////////////////////////////////
+
+ // Style handling
+
+ public void disableStyle() {
+ style = false;
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ children[i].disableStyle();
+ }
+ }
+ }
+
+
+ public void enableStyle() {
+ style = true;
+ if (family == GROUP) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ children[i].enableStyle();
+ }
+ }
+ }
+
+
+ protected void styles(PGraphics g) {
+ // Nothing to do here. The styles are set in the drawGeometry() method.
+ }
+
+
+ public boolean is3D() {
+ return true;
+ }
+
+ ///////////////////////////////////////////////////////////
+
+ //
+
+ // Drawing methods
+
+
+ public void draw() {
+ draw(pgl);
+ }
+
+
+ public void draw(PGraphics g) {
+ if (visible) {
+
+ if (matrix != null) {
+ g.pushMatrix();
+ g.applyMatrix(matrix);
+ }
+
+ if (family == GROUP) {
+
+ init();
+ for (int i = 0; i < childCount; i++) {
+ ((PShape3D)children[i]).draw(g);
+ }
+
+ } else {
+ drawGeometry(g);
+ }
+
+ if (matrix != null) {
+ g.popMatrix();
+ }
+ }
+ }
+
+
+ protected void pre(PGraphics g) {
+ if (matrix != null) {
+ g.pushMatrix();
+ g.applyMatrix(matrix);
+ }
+ // No need to push and set styles.
+ }
+
+
+ public void post(PGraphics g) {
+ if (matrix != null) {
+ g.popMatrix();
+ }
+ }
+
+
+ public void drawImpl(PGraphics g) {
+ if (family == GROUP) {
+ drawGroup(g);
+ } else {
+ drawGeometry(g);
+ }
+ }
+
+
+ protected void drawGroup(PGraphics g) {
+ init();
+ for (int i = 0; i < childCount; i++) {
+ children[i].draw(g);
+ }
+ }
+
+
+ protected void drawGeometry(PGraphics g) {
+ int numTextures;
+ float pointSize;
+
+ // Setting line width and point size from stroke value, using
+ // either the group's weight or the renderer's weight.
+ if (0 < strokeWeight && style) {
+ gl.glLineWidth(strokeWeight);
+ pointSize = PApplet.min(strokeWeight, PGraphicsOpenGL2.maxPointSize);
+ } else {
+ gl.glLineWidth(g.strokeWeight);
+ pointSize = PApplet.min(g.strokeWeight, PGraphicsOpenGL2.maxPointSize);
+ }
+ if (!pointSprites) {
+ // Point sprites use their own size variable (set below).
+ gl.glPointSize(pointSize);
+ }
+
+ gl.glEnableClientState(GL2.GL_NORMAL_ARRAY);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glNormalBufferID);
+ gl.glNormalPointer(GL.GL_FLOAT, 0, 0);
+
+ if (style) {
+ gl.glEnableClientState(GL2.GL_COLOR_ARRAY);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glColorBufferID);
+ gl.glColorPointer(4, GL.GL_FLOAT, 0, 0);
+ }
+
+ gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glVertexBufferID);
+ gl.glVertexPointer(3, GL.GL_FLOAT, 0, 0);
+
+ numTextures = 0;
+ if (style) {
+ for (int t = 0; t < textures.length; t++) {
+ if (textures[t] != null) {
+ PTexture tex = (PTexture)textures[t].getCache(pgl);
+ tex = pgl.getTexture(textures[t]);
+ if (tex == null) {
+ break;
+ }
+
+ gl.glEnable(tex.getGLTarget());
+ gl.glActiveTexture(GL.GL_TEXTURE0 + t);
+ gl.glBindTexture(tex.getGLTarget(), tex.getGLID());
+ renderTextures[numTextures] = tex;
+ numTextures++;
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (0 < numTextures) {
+ if (pointSprites) {
+ // Texturing with point sprites.
+
+ // 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 and is not clamped to the
+ // minimum and maximum point sizes.
+ gl.glPointParameterf(GL2.GL_POINT_FADE_THRESHOLD_SIZE, 0.6f * maxSpriteSize);
+ gl.glPointParameterf(GL2.GL_POINT_SIZE_MIN, 1.0f);
+ gl.glPointParameterf(GL2.GL_POINT_SIZE_MAX, maxSpriteSize);
+ gl.glPointSize(maxSpriteSize);
+
+ // This is how will our point sprite's size will be modified by
+ // distance from the viewer:
+ // actualSize = pointSize / sqrt(p[0] + p[1] * d + p[2] * d * d)
+ // where pointSize is the value set with glPointSize(), clamped to the extreme values
+ // in glPointParameterf(GL.GL_POINT_SIZE_MIN/GL.GL_POINT_SIZE_MAX.
+ // d is the distance from the point sprite to the camera and p is the array parameter
+ // passed in the following call:
+ gl.glPointParameterfv(GL2.GL_POINT_DISTANCE_ATTENUATION, spriteDistAtt, 0);
+
+ // Specify point sprite texture coordinate replacement mode for each
+ // texture unit
+ gl.glTexEnvf(GL2.GL_POINT_SPRITE, GL2.GL_COORD_REPLACE, GL.GL_TRUE);
+
+ gl.glEnable(GL2.GL_POINT_SPRITE);
+ } else {
+ // Regular texturing.
+ gl.glEnableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+ for (int t = 0; t < numTextures; t++) {
+ gl.glClientActiveTexture(GL.GL_TEXTURE0 + t);
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, glTexCoordBufferID[t]);
+ gl.glTexCoordPointer(2, GL.GL_FLOAT, 0, 0);
+ }
+ if (1 < numTextures) {
+ pgl.setMultitextureBlend(renderTextures, numTextures);
+ }
+ }
+ }
+
+ if (!style) {
+ // Using fill or tint color when the style is disabled.
+ if (0 < numTextures) {
+ if (g.tint) {
+ pgl.setTintColor();
+ } else {
+ gl.glColor4f(1, 1, 1, 1);
+ }
+ } else {
+ pgl.setFillColor();
+ }
+ }
+
+ gl.glDrawArrays(glMode, firstVertex, lastVertex - firstVertex + 1);
+
+ if (0 < numTextures) {
+ if (1 < numTextures) {
+ pgl.clearMultitextureBlend(numTextures);
+ }
+ if (pointSprites) {
+ gl.glDisable(GL2.GL_POINT_SPRITE);
+ } else {
+ gl.glDisableClientState(GL2.GL_TEXTURE_COORD_ARRAY);
+ }
+ for (int t = 0; t < numTextures; t++) {
+ PTexture tex = renderTextures[t];
+ gl.glActiveTexture(GL.GL_TEXTURE0 + t);
+ gl.glBindTexture(tex.getGLTarget(), 0);
+ }
+ for (int t = 0; t < numTextures; t++) {
+ PTexture tex = renderTextures[t];
+ gl.glDisable(tex.getGLTarget());
+ }
+ }
+
+ gl.glBindBuffer(GL.GL_ARRAY_BUFFER, 0);
+ gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);
+ gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
+ gl.glDisableClientState(GL2.GL_NORMAL_ARRAY);
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ // Parameters
+
+ static public Parameters newParameters() {
+ return new Parameters();
+ }
+
+
+ static public Parameters newParameters(int drawMode) {
+ return new Parameters(drawMode);
+ }
+
+
+ static public Parameters newParameters(int drawMode, int updateMode) {
+ return new Parameters(drawMode, updateMode);
+ }
+
+
+ static public class Parameters extends PParameters {
+ public int drawMode;
+ public int updateMode;
+
+ public Parameters() {
+ drawMode= POINTS;
+ updateMode = STATIC;
+ }
+
+ public Parameters(int drawMode) {
+ this.drawMode= drawMode;
+ updateMode = STATIC;
+ }
+
+ public Parameters(int drawMode, int updateMode) {
+ this.drawMode= drawMode;
+ this.updateMode = updateMode;
+ }
+
+ public Parameters(Parameters src) {
+ drawMode= src.drawMode;
+ updateMode = src.updateMode;
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ // OBJ loading
+
+
+ protected BufferedReader getBufferedReader(String filename) {
+ BufferedReader retval = papplet.createReader(filename);
+ if (retval != null) {
+ return retval;
+ } else {
+ PApplet.println("Could not find this file " + filename);
+ return null;
+ }
+ }
+
+
+ protected void parseOBJ(BufferedReader reader, ArrayList vertices, ArrayList normals, ArrayList textures, ArrayList faces, ArrayList materials) {
+ Hashtable mtlTable = new Hashtable();
+ int mtlIdxCur = -1;
+ boolean readv, readvn, readvt;
+ try {
+ // Parse the line.
+
+ readv = readvn = readvt = false;
+ String line;
+ String gname = "object";
+ while ((line = reader.readLine()) != null) {
+
+ // The below patch/hack comes from Carlos Tomas Marti and is a
+ // fix for single backslashes in Rhino obj files
+
+ // BEGINNING OF RHINO OBJ FILES HACK
+ // Statements can be broken in multiple lines using '\' at the
+ // end of a line.
+ // In regular expressions, the backslash is also an escape
+ // character.
+ // The regular expression \\ matches a single backslash. This
+ // regular expression as a Java string, becomes "\\\\".
+ // That's right: 4 backslashes to match a single one.
+ while (line.contains("\\")) {
+ line = line.split("\\\\")[0];
+ final String s = reader.readLine();
+ if (s != null)
+ line += s;
+ }
+ // END OF RHINO OBJ FILES HACK
+
+ String[] elements = line.split("\\s+");
+ // if not a blank line, process the line.
+ if (elements.length > 0) {
+ if (elements[0].equals("v")) {
+ // vertex
+ PVector tempv = new PVector(Float.valueOf(elements[1]).floatValue(), Float.valueOf(elements[2]).floatValue(), Float.valueOf(elements[3]).floatValue());
+ vertices.add(tempv);
+ readv = true;
+ } else if (elements[0].equals("vn")) {
+ // normal
+ PVector tempn = new PVector(Float.valueOf(elements[1]).floatValue(), Float.valueOf(elements[2]).floatValue(), Float.valueOf(elements[3]).floatValue());
+ normals.add(tempn);
+ readvn = true;
+ } else if (elements[0].equals("vt")) {
+ // uv
+ PVector tempv = new PVector(Float.valueOf(elements[1]).floatValue(), Float.valueOf(elements[2]).floatValue());
+ textures.add(tempv);
+ readvt = true;
+ } else if (elements[0].equals("o")) {
+ // Object name is ignored, for now.
+ } else if (elements[0].equals("mtllib")) {
+ if (elements[1] != null) {
+ parseMTL(getBufferedReader(elements[1]), materials, mtlTable);
+ }
+ } else if (elements[0].equals("g")) {
+ gname = elements[1];
+ } else if (elements[0].equals("usemtl")) {
+ // Getting index of current active material (will be applied on all subsequent faces)..
+ if (elements[1] != null) {
+ String mtlname = elements[1];
+ if (mtlTable.containsKey(mtlname)) {
+ Integer tempInt = mtlTable.get(mtlname);
+ mtlIdxCur = tempInt.intValue();
+ } else {
+ mtlIdxCur = -1;
+ }
+ }
+ } else if (elements[0].equals("f")) {
+ // Face setting
+ OBJFace face = new OBJFace();
+ face.matIdx = mtlIdxCur;
+ face.name = gname;
+
+ for (int i = 1; i < elements.length; i++) {
+ String seg = elements[i];
+
+ if (seg.indexOf("/") > 0) {
+ String[] forder = seg.split("/");
+
+ if (forder.length > 2) {
+ // Getting vertex and texture and normal indexes.
+ if (forder[0].length() > 0 && readv) {
+ face.vertIdx.add(Integer.valueOf(forder[0]));
+ }
+
+ if (forder[1].length() > 0 && readvt) {
+ face.texIdx.add(Integer.valueOf(forder[1]));
+ }
+
+ if (forder[2].length() > 0 && readvn) {
+ face.normIdx.add(Integer.valueOf(forder[2]));
+ }
+ } else if (forder.length > 1) {
+ // Getting vertex and texture/normal indexes.
+ if (forder[0].length() > 0 && readv) {
+ face.vertIdx.add(Integer.valueOf(forder[0]));
+ }
+
+ if (forder[1].length() > 0) {
+ if (readvt) {
+ face.texIdx.add(Integer.valueOf(forder[1]));
+ } else if (readvn) {
+ face.normIdx.add(Integer.valueOf(forder[1]));
+ }
+
+ }
+
+ } else if (forder.length > 0) {
+ // Getting vertex index only.
+ if (forder[0].length() > 0 && readv) {
+ face.vertIdx.add(Integer.valueOf(forder[0]));
+ }
+ }
+ } else {
+ // Getting vertex index only.
+ if (seg.length() > 0 && readv) {
+ face.vertIdx.add(Integer.valueOf(seg));
+ }
+ }
+ }
+
+ faces.add(face);
+
+ }
+ }
+ }
+
+ if (materials.size() == 0) {
+ // No materials definition so far. Adding one default material.
+ OBJMaterial defMtl = new OBJMaterial();
+ materials.add(defMtl);
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+ protected void parseMTL(BufferedReader reader, ArrayList materials, Hashtable materialsHash) {
+ try {
+ String line;
+ OBJMaterial currentMtl = null;
+ while ((line = reader.readLine()) != null) {
+ // Parse the line
+ line = line.trim();
+
+ String elements[] = line.split("\\s+");
+
+ if (elements.length > 0) {
+ // Extract the material data.
+
+ if (elements[0].equals("newmtl")) {
+ // Starting new material.
+ String mtlname = elements[1];
+ currentMtl = new OBJMaterial(mtlname);
+ materialsHash.put(mtlname, new Integer(materials.size()));
+ materials.add(currentMtl);
+ } else if (elements[0].equals("map_Kd") && elements.length > 1) {
+ // Loading texture map.
+ String texname = elements[1];
+ currentMtl.kdMap = papplet.loadImage(texname);
+ } else if (elements[0].equals("Ka") && elements.length > 3) {
+ // The ambient color of the material
+ currentMtl.ka.x = Float.valueOf(elements[1]).floatValue();
+ currentMtl.ka.y = Float.valueOf(elements[2]).floatValue();
+ currentMtl.ka.z = Float.valueOf(elements[3]).floatValue();
+ } else if (elements[0].equals("Kd") && elements.length > 3) {
+ // The diffuse color of the material
+ currentMtl.kd.x = Float.valueOf(elements[1]).floatValue();
+ currentMtl.kd.y = Float.valueOf(elements[2]).floatValue();
+ currentMtl.kd.z = Float.valueOf(elements[3]).floatValue();
+ } else if (elements[0].equals("Ks") && elements.length > 3) {
+ // The specular color weighted by the specular coefficient
+ currentMtl.ks.x = Float.valueOf(elements[1]).floatValue();
+ currentMtl.ks.y = Float.valueOf(elements[2]).floatValue();
+ currentMtl.ks.z = Float.valueOf(elements[3]).floatValue();
+ } else if ((elements[0].equals("d") || elements[0].equals("Tr")) && elements.length > 1) {
+ // Reading the alpha transparency.
+ currentMtl.d = Float.valueOf(elements[1]).floatValue();
+ } else if (elements[0].equals("Ns") && elements.length > 1) {
+ // The specular component of the Phong shading model
+ currentMtl.ns = Float.valueOf(elements[1]).floatValue();
+ }
+
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ protected void recordOBJ() {
+ recordOBJ(objVertices, objNormal, objTexCoords, objFaces, objMaterials);
+ objVertices = null;
+ objNormal = null;
+ objTexCoords = null;
+ objFaces = null;
+ objMaterials = null;
+
+ readFromOBJ = false;
+ }
+
+ protected void recordOBJ(ArrayList vertices, ArrayList normals, ArrayList textures, ArrayList faces, ArrayList materials) {
+ int mtlIdxCur = -1;
+ OBJMaterial mtl = null;
+
+ pgl.saveDrawingState();
+
+ // The recorded shapes are not merged, they are grouped
+ // according to the group names found in the OBJ file.
+ pgl.mergeRecShapes = false;
+
+ // Using RGB mode for coloring.
+ pgl.colorMode = RGB;
+
+ // Strokes are not used to draw the model.
+ pgl.stroke = false;
+
+ // Normals are automatically computed if not specified in the OBJ file.
+ pgl.autoNormal(true);
+
+ // Using normal mode for texture coordinates (i.e.: normalized between 0 and 1).
+ pgl.textureMode = NORMAL;
+
+ pgl.beginShapeRecorderImpl();
+ for (int i = 0; i < faces.size(); i++) {
+ OBJFace face = (OBJFace) faces.get(i);
+
+ // Getting current material.
+ if (mtlIdxCur != face.matIdx) {
+ mtlIdxCur = PApplet.max(0, face.matIdx); // To make sure that at least we get the default material.
+
+ mtl = (OBJMaterial) materials.get(mtlIdxCur);
+
+ // Setting colors.
+ pgl.specular(mtl.ks.x * 255.0f, mtl.ks.y * 255.0f, mtl.ks.z * 255.0f);
+ pgl.ambient(mtl.ka.x * 255.0f, mtl.ka.y * 255.0f, mtl.ka.z * 255.0f);
+ if (pgl.fill) {
+ pgl.fill(mtl.kd.x * 255.0f, mtl.kd.y * 255.0f, mtl.kd.z * 255.0f, mtl.d * 255.0f);
+ }
+ pgl.shininess(mtl.ns);
+
+ if (pgl.tint && mtl.kdMap != null) {
+ // If current material is textured, then tinting the texture using the diffuse color.
+ pgl.tint(mtl.kd.x * 255.0f, mtl.kd.y * 255.0f, mtl.kd.z * 255.0f, mtl.d * 255.0f);
+ }
+ }
+
+ // Recording current face.
+ if (face.vertIdx.size() == 3) {
+ pgl.beginShape(TRIANGLES); // Face is a triangle, so using appropriate shape kind.
+ } else if (face.vertIdx.size() == 4) {
+ pgl.beginShape(QUADS); // Face is a quad, so using appropriate shape kind.
+ } else {
+ pgl.beginShape();
+ }
+
+ pgl.shapeName(face.name);
+
+ for (int j = 0; j < face.vertIdx.size(); j++){
+ int vertIdx, normIdx;
+ PVector vert, norms;
+
+ vert = norms = null;
+
+ vertIdx = face.vertIdx.get(j).intValue() - 1;
+ vert = (PVector) vertices.get(vertIdx);
+
+ if (j < face.normIdx.size()) {
+ normIdx = face.normIdx.get(j).intValue() - 1;
+ if (-1 < normIdx) {
+ norms = (PVector) normals.get(normIdx);
+ }
+ }
+
+ if (mtl != null && mtl.kdMap != null) {
+ // This face is textured.
+ int texIdx;
+ PVector tex = null;
+
+ if (j < face.texIdx.size()) {
+ texIdx = face.texIdx.get(j).intValue() - 1;
+ if (-1 < texIdx) {
+ tex = (PVector) textures.get(texIdx);
+ }
+ }
+
+ PTexture texMtl = (PTexture)mtl.kdMap.getCache(pgl);
+ if (texMtl != null) {
+ // Texture orientation in Processing is inverted.
+ texMtl.setFlippedY(true);
+ }
+ pgl.texture(mtl.kdMap);
+ if (norms != null) {
+ pgl.normal(norms.x, norms.y, norms.z);
+ }
+ if (tex != null) {
+ pgl.vertex(vert.x, vert.y, vert.z, tex.x, tex.y);
+ } else {
+ pgl.vertex(vert.x, vert.y, vert.z);
+ }
+ } else {
+ // This face is not textured.
+ if (norms != null) {
+ pgl.normal(norms.x, norms.y, norms.z);
+ }
+ pgl.vertex(vert.x, vert.y, vert.z);
+ }
+ }
+ pgl.endShape(CLOSE);
+ }
+
+ // Allocate space for the geometry that the triangulator has generated from the OBJ model.
+ allocateShape(pgl.recordedVertices.size());
+ updateElement = -1;
+
+ width = height = depth = 0;
+ xmin = ymin = zmin = 10000;
+ xmax = ymax = zmax = -10000;
+
+ pgl.endShapeRecorderImpl(this);
+
+ pgl.restoreDrawingState();
+ }
+
+
+ protected class OBJFace {
+ ArrayList vertIdx;
+ ArrayList texIdx;
+ ArrayList normIdx;
+ int matIdx;
+ String name;
+
+ OBJFace() {
+ vertIdx = new ArrayList();
+ texIdx = new ArrayList();
+ normIdx = new ArrayList();
+ matIdx = -1;
+ name = "";
+ }
+ }
+
+ protected class OBJMaterial {
+ String name;
+ PVector ka;
+ PVector kd;
+ PVector ks;
+ float d;
+ float ns;
+ PImage kdMap;
+
+ OBJMaterial() {
+ this("default");
+ }
+
+ OBJMaterial(String name) {
+ this.name = name;
+ ka = new PVector(0.5f, 0.5f, 0.5f);
+ kd = new PVector(0.5f, 0.5f, 0.5f);
+ ks = new PVector(0.5f, 0.5f, 0.5f);
+ d = 1.0f;
+ ns = 0.0f;
+ kdMap = null;
+ }
+ }
+}
diff --git a/java/libraries/opengl2/src/processing/opengl2/PTexture.java b/java/libraries/opengl2/src/processing/opengl2/PTexture.java
new file mode 100644
index 000000000..1028bd2c6
--- /dev/null
+++ b/java/libraries/opengl2/src/processing/opengl2/PTexture.java
@@ -0,0 +1,1088 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Part of the Processing project - http://processing.org
+
+ Copyright (c) 2010 Ben Fry and Casey Reas
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License version 2.1 as published by the Free Software Foundation.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.opengl2;
+
+import javax.media.opengl.GL;
+import javax.media.opengl.GL2;
+import java.lang.reflect.Method;
+import java.nio.*;
+import processing.core.PApplet;
+import processing.core.PConstants;
+import processing.core.PImage;
+import processing.core.PMetadata;
+import processing.core.PParameters;
+
+
+/**
+ * This class wraps an OpenGL texture.
+ * By Andres Colubri
+ *
+ */
+@SuppressWarnings("unused")
+public class PTexture extends PMetadata implements PConstants {
+ public int width, height;
+
+ protected PApplet parent;
+ protected PGraphicsOpenGL2 pgl;
+ protected GL gl;
+
+ protected int glID;
+ protected int glTarget;
+ protected int glFormat;
+ protected int glMinFilter;
+ protected int glMagFilter;
+ protected int glWrapS;
+ protected int glWrapT;
+
+ protected int glWidth;
+ protected int glHeight;
+
+ protected boolean usingMipmaps;
+ protected float maxTexCoordU;
+ protected float maxTexCoordV;
+
+ protected boolean flippedX;
+ protected boolean flippedY;
+
+ protected int[] tempPixels = null;
+ protected PFramebuffer tempFbo = null;
+
+ ////////////////////////////////////////////////////////////
+
+ // Constructors.
+
+
+ /**
+ * Creates an instance of PTexture with size width x height. The texture is
+ * initialized (empty) to that size.
+ * @param parent PApplet
+ * @param width int
+ * @param height int
+ */
+ public PTexture(PApplet parent, int width, int height) {
+ this(parent, width, height, new Parameters());
+ }
+
+
+ /**
+ * Creates an instance of PTexture with size width x height and with the specified parameters.
+ * The texture is initialized (empty) to that size.
+ * @param parent PApplet
+ * @param width int
+ * @param height int
+ * @param params Parameters
+ */
+ public PTexture(PApplet parent, int width, int height, PParameters params) {
+ this.parent = parent;
+ this.width = width;
+ this.height = height;
+
+ pgl = (PGraphicsOpenGL2)parent.g;
+ gl = pgl.gl;
+
+ glID = 0;
+
+ setParameters((Parameters)params);
+ createTexture(width, height);
+ }
+
+
+ /**
+ * Creates an instance of PTexture using image file filename as source.
+ * @param parent PApplet
+ * @param filename String
+ */
+ public PTexture(PApplet parent, String filename) {
+ this(parent, filename, new Parameters());
+ }
+
+
+ /**
+ * Creates an instance of PTexture using image file filename as source and the specified texture parameters.
+ * @param parent PApplet
+ * @param filename String
+ * @param params Parameters
+ */
+ public PTexture(PApplet parent, String filename, PParameters params) {
+ this.parent = parent;
+
+ pgl = (PGraphicsOpenGL2)parent.g;
+ gl = pgl.gl;
+
+ glID = 0;
+
+ PImage img = parent.loadImage(filename);
+ setParameters((Parameters)params);
+ set(img);
+ }
+
+
+ public void delete() {
+ deleteTexture();
+ }
+
+ ////////////////////////////////////////////////////////////
+
+ // Init, resize methods
+
+
+ /**
+ * Sets the size of the image and texture to width x height. If the texture is already initialized,
+ * it first destroys the current OpenGL texture object and then creates a new one with the specified
+ * size.
+ * @param width int
+ * @param height int
+ */
+ public void init(int width, int height) {
+ init(width, height, new Parameters());
+ }
+
+
+ /**
+ * Sets the size of the image and texture to width x height, and the parameters of the texture to params.
+ * If the texture is already initialized, it first destroys the current OpenGL texture object and then creates
+ * a new one with the specified size.
+ * @param width int
+ * @param height int
+ * @param params GLTextureParameters
+ */
+ public void init(int width, int height, Parameters params) {
+ this.width = width;
+ this.height = height;
+ setParameters(params);
+ createTexture(width, height);
+ }
+
+
+ public void resize(int wide, int high) {
+ // Creating new texture with the appropriate size.
+ PTexture tex = new PTexture(parent, wide, high, getParameters());
+
+ // Copying the contents of this texture into tex.
+ tex.set(this);
+
+ // Releasing the opengl resources associated to "this".
+ this.delete();
+
+ // Now, overwriting "this" with tex.
+ copyObject(tex);
+
+ // Nullifying some utility objects so they are recreated with the appropriate
+ // size when needed.
+ tempPixels = null;
+ tempFbo = null;
+ }
+
+
+ /**
+ * Returns true if the texture has been initialized.
+ * @return boolean
+ */
+ public boolean available() {
+ return 0 < glID;
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Set methods
+
+
+ public void set(PImage img) {
+ PTexture tex = (PTexture)img.getCache(pgl);
+ set(tex);
+ }
+
+
+ public void set(PImage img, int x, int y, int w, int h) {
+ PTexture tex = (PTexture)img.getCache(pgl);
+ set(tex, x, y, w, h);
+ }
+
+
+ public void set(PTexture tex) {
+ copyTexels(tex, 0, 0, tex.width, tex.height, true);
+ }
+
+
+ public void set(PTexture tex, int x, int y, int w, int h) {
+ copyTexels(tex, x, y, w, h, true);
+ }
+
+
+ public void set(int[] pixels) {
+ set(pixels, 0, 0, width, height, ARGB);
+ }
+
+
+ public void set(int[] pixels, int format) {
+ set(pixels, 0, 0, width, height, format);
+ }
+
+
+ public void set(int[] pixels, int x, int y, int w, int h) {
+ set(pixels, x, y, w, h, ARGB);
+ }
+
+
+ public void set(int[] pixels, int x, int y, int w, int h, int format) {
+ // TODO: Should we throw exceptions here or just a warning?
+ if (pixels == null) {
+ throw new RuntimeException("PTexture: null pixels array");
+ }
+ if (pixels.length != w * h) {
+ throw new RuntimeException("PTexture: wrong length of pixels array");
+ }
+
+ if (glID == 0) {
+ createTexture(width, height);
+ }
+
+ gl.glEnable(glTarget);
+ gl.glBindTexture(glTarget, glID);
+
+ if (usingMipmaps) {
+ if (PGraphicsOpenGL2.mipmapGeneration) {
+ // Automatic mipmap generation.
+ int[] rgbaPixels = new int[w * h];
+ convertToRGBA(pixels, rgbaPixels, format, w, h);
+ gl.glTexParameteri(GL.GL_TEXTURE_2D, GL2.GL_GENERATE_MIPMAP, GL.GL_TRUE);
+ setTexels(x, y, w, h, rgbaPixels);
+ } else {
+ // TODO: Manual mipmap generation.
+ // Open source implementation of gluBuild2DMipmaps here:
+ // http://code.google.com/p/glues/source/browse/trunk/glues/source/glues_mipmap.c
+ }
+ } else {
+ int[] rgbaPixels = new int[w * h];
+ convertToRGBA(pixels, rgbaPixels, format, w, h);
+ setTexels(x, y, w, h, rgbaPixels);
+ }
+
+ gl.glBindTexture(glTarget, 0);
+ gl.glDisable(glTarget);
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Get methods
+
+
+ /**
+ * Copy texture to pixels. Involves video memory to main memory transfer (slow).
+ */
+ public void get(int[] pixels) {
+ // TODO: here is ok to create a new pixels array, or an error/warning
+ // should be thrown instead?
+ if ((pixels == null) || (pixels.length != width * height)) {
+ pixels = new int[width * height];
+ }
+
+ int size = glWidth * glHeight;
+
+ if (tempFbo == null) {
+ tempFbo = new PFramebuffer(parent, glWidth, glHeight);
+ }
+
+ if (PGraphicsOpenGL2.fboSupported) {
+ // Attaching the texture to the color buffer of a FBO, binding the FBO and reading the pixels
+ // from the current draw buffer (which is the color buffer of the FBO).
+ tempFbo.setColorBuffer(this);
+ pgl.pushFramebuffer();
+ pgl.setFramebuffer(tempFbo);
+ tempFbo.readPixels();
+ pgl.popFramebuffer();
+ } else {
+ // Here we don't have FBOs, so the method above is of no use. What we do instead is
+ // to draw the texture to the screen framebuffer, and then grab the pixels from there.
+ pgl.pushFramebuffer();
+ pgl.setFramebuffer(tempFbo);
+ pgl.drawTexture(this, 0, 0, glWidth, glHeight, 0, 0, glWidth, glHeight);
+ tempFbo.readPixels();
+ pgl.popFramebuffer();
+ }
+
+ if (tempPixels == null) {
+ tempPixels = new int[size];
+ }
+ tempFbo.getPixels(tempPixels);
+
+ convertToARGB(tempPixels, pixels);
+ if (flippedX) flipArrayOnX(pixels, 1);
+ if (flippedY) flipArrayOnY(pixels, 1);
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Put methods (the source texture is not resized to cover the entire
+ // destination).
+
+
+ public void put(PTexture tex) {
+ copyTexels(tex, 0, 0, tex.width, tex.height, false);
+ }
+
+
+ public void put(PTexture tex, int x, int y, int w, int h) {
+ copyTexels(tex, x, y, w, h, false);
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Get OpenGL parameters
+
+
+ protected int getGLWidth() {
+ return glWidth;
+ }
+
+
+ protected int getGLHeight() {
+ return glHeight;
+ }
+
+
+ /**
+ * Provides the ID of the OpenGL texture object.
+ * @return int
+ */
+ protected int getGLID() {
+ return glID;
+ }
+
+
+ /**
+ * Returns the texture target.
+ * @return int
+ */
+ protected int getGLTarget() {
+ return glTarget;
+ }
+
+
+ /**
+ * Returns the texture internal format.
+ * @return int
+ */
+ protected int getGLFormat() {
+ return glFormat;
+ }
+
+
+ /**
+ * Returns the texture minimization filter.
+ * @return int
+ */
+ protected int getGLMinFilter() {
+ return glMinFilter;
+ }
+
+
+ /**
+ * Returns the texture magnification filter.
+ * @return int
+ */
+ protected int getGLMagFilter() {
+ return glMagFilter;
+ }
+
+ /**
+ * Returns the texture wrapping mode for the S coordinate.
+ * @return int
+ */
+ protected int getGLWrapS() {
+ return glWrapS;
+ }
+
+ /**
+ * Returns the texture wrapping mode for the T coordinate.
+ * @return int
+ */
+ protected int getGLWrapT() {
+ return glWrapT;
+ }
+
+ /**
+ * Returns true or false whether or not the texture is using mipmaps.
+ * @return boolean
+ */
+ protected boolean usingMipmaps() {
+ return usingMipmaps;
+ }
+
+
+ /**
+ * Returns the maximum possible value for the texture coordinate U (horizontal).
+ * @return float
+ */
+ protected float getMaxTexCoordU() {
+ return maxTexCoordU;
+ }
+
+
+ /**
+ * Returns the maximum possible value for the texture coordinate V (vertical).
+ * @return float
+ */
+ protected float getMaxTexCoordV() {
+ return maxTexCoordV;
+ }
+
+
+ /**
+ * Returns true if the texture is flipped along the horizontal direction.
+ * @return boolean;
+ */
+ protected boolean isFlippedX() {
+ return flippedX;
+ }
+
+
+ /**
+ * Sets the texture as flipped or not flipped on the horizontal direction.
+ * @param v boolean;
+ */
+ protected void setFlippedX(boolean v) {
+ flippedX = v;
+ }
+
+
+ /**
+ * Returns true if the texture is flipped along the vertical direction.
+ * @return boolean;
+ */
+ protected boolean isFlippedY() {
+ return flippedY;
+ }
+
+
+ /**
+ * Sets the texture as flipped or not flipped on the vertical direction.
+ * @param v boolean;
+ */
+ public void setFlippedY(boolean v) {
+ flippedY = v;
+ }
+
+
+ ////////////////////////////////////////////////////////////
+
+ // Utilities
+
+ // bit shifting this might be more efficient
+ private int nextPowerOfTwo(int val) {
+ int ret = 1;
+ while (ret < val) {
+ ret <<= 1;
+ }
+ return ret;
+ }
+
+
+ /**
+ * Flips intArray along the X axis.
+ * @param intArray int[]
+ * @param mult int
+ */
+ protected void flipArrayOnX(int[] intArray, int mult) {
+ int index = 0;
+ int xindex = mult * (width - 1);
+ for (int x = 0; x < width / 2; x++) {
+ for (int y = 0; y < height; y++) {
+ int i = index + mult * y * width;
+ int j = xindex + mult * y * width;
+
+ for (int c = 0; c < mult; c++) {
+ int temp = intArray[i];
+ intArray[i] = intArray[j];
+ intArray[j] = temp;
+
+ i++;
+ j++;
+ }
+
+ }
+ index += mult;
+ xindex -= mult;
+ }
+ }
+
+
+ /**
+ * Flips intArray along the Y axis.
+ * @param intArray int[]
+ * @param mult int
+ */
+ protected void flipArrayOnY(int[] intArray, int mult) {
+ int index = 0;
+ int yindex = mult * (height - 1) * width;
+ for (int y = 0; y < height / 2; y++) {
+ for (int x = 0; x < mult * width; x++) {
+ int temp = intArray[index];
+ intArray[index] = intArray[yindex];
+ intArray[yindex] = temp;
+
+ index++;
+ yindex++;
+ }
+ yindex -= mult * width * 2;
+ }
+ }
+
+
+ /**
+ * Reorders a pixel array in the given format into the order required by OpenGL (RGBA).
+ * Both arrays are assumed to be of the same length. The width and height parameters
+ * are used in the YUV420 to RBGBA conversion.
+ * @param intArray int[]
+ * @param tIntArray int[]
+ * @param arrayFormat int
+ * @param w int
+ * @param h int
+ */
+ protected void convertToRGBA(int[] intArray, int[] tIntArray, int arrayFormat, int w, int h) {
+ if (PGraphicsOpenGL2.BIG_ENDIAN) {
+ switch (arrayFormat) {
+ case ALPHA:
+
+ // Converting from xxxA into RGBA. RGB is set to white
+ // (0xFFFFFF, i.e.: (255, 255, 255))
+ for (int i = 0; i< intArray.length; i++) {
+ tIntArray[i] = 0xFFFFFF00 | intArray[i];
+ }
+ break;
+
+ case RGB:
+
+ // Converting xRGB into RGBA. A is set to 0xFF (255, full opacity).
+ for (int i = 0; i< intArray.length; i++) {
+ int pixel = intArray[i];
+ tIntArray[i] = (pixel << 8) | 0xFF;
+ }
+ break;
+
+ case ARGB:
+
+ // Converting ARGB into RGBA. Shifting RGB to 8 bits to the left,
+ // and bringing A to the first byte.
+ for (int i = 0; i< intArray.length; i++) {
+ int pixel = intArray[i];
+ tIntArray[i] = (pixel << 8) | ((pixel >> 24) & 0xFF);
+ }
+ break;
+ }
+
+ } else {
+ // LITTLE_ENDIAN
+ // ARGB native, and RGBA opengl means ABGR on windows
+ // for the most part just need to swap two components here
+ // the sun.cpu.endian here might be "false", oddly enough..
+ // (that's why just using an "else", rather than check for "little")
+
+ switch (arrayFormat) {
+ case ALPHA:
+
+ // Converting xxxA into ARGB, with RGB set to white.
+ for (int i = 0; i< intArray.length; i++) {
+ tIntArray[i] = (intArray[i] << 24) | 0x00FFFFFF;
+ }
+ break;
+
+ case RGB:
+
+ // We need to convert xRGB into ABGR,
+ // so R and B must be swapped, and the x just made 0xFF.
+ for (int i = 0; i< intArray.length; i++) {
+ int pixel = intArray[i];
+ tIntArray[i] = 0xFF000000 |
+ ((pixel & 0xFF) << 16) |
+ ((pixel & 0xFF0000) >> 16) |
+ (pixel & 0x0000FF00);
+ }
+ break;
+
+ case ARGB:
+
+ // We need to convert ARGB into ABGR,
+ // so R and B must be swapped, A and G just brought back in.
+ for (int i = 0; i < intArray.length; i++) {
+ int pixel = intArray[i];
+ tIntArray[i] = ((pixel & 0xFF) << 16) |
+ ((pixel & 0xFF0000) >> 16) |
+ (pixel & 0xFF00FF00);
+ }
+ break;
+
+ }
+
+ }
+ }
+
+
+ /**
+ * Reorders a pixel array in a given format into ARGB. The input array must be
+ * of size width * height, while the output array must be of glWidth * glHeight.
+ * @param intArray int[]
+ * @param intArray int[]
+ * @param arrayFormat int
+ */
+ protected void convertToARGB(int[] intArray, int[] tIntArray, int arrayFormat) {
+ int t = 0;
+ int p = 0;
+ int pixel;
+
+ switch (arrayFormat) {
+ case ALPHA:
+
+ // xxxA to ARGB, setting RGB to black.
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ pixel = intArray[p++];
+ tIntArray[t++] = (pixel << 24) & 0xFF000000;
+ }
+ t += glWidth - width;
+ }
+
+ break;
+
+ case RGB:
+
+ // xRGB to ARGB, setting A to be 0xFF.
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ pixel = intArray[p++];
+ tIntArray[t++] = pixel | 0xFF000000;
+ }
+ t += glWidth - width;
+ }
+
+ break;
+
+ case ARGB:
+
+ // ARGB to ARGB, where the source is smaller than the destination.
+ for (int y = 0; y < height; y++) {
+ PApplet.arrayCopy(intArray, width * y, tIntArray, glWidth * y, width);
+ }
+
+ break;
+
+ }
+
+ }
+
+
+ /**
+ * Reorders an OpenGL pixel array (RGBA) into ARGB. The input array must be
+ * of size glWidth * glHeight, while the resulting array of size width * height.
+ * @param intArray int[]
+ * @param intArray int[]
+ */
+ protected void convertToARGB(int[] intArray, int[] tIntArray) {
+ int t = 0;
+ int p = 0;
+ if (PGraphicsOpenGL2.BIG_ENDIAN) {
+
+ // RGBA to ARGB conversion: shifting RGB 8 bits to the right,
+ // and placing A 24 bits to the left.
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int pixel = intArray[p++];
+ tIntArray[t++] = (pixel >> 8) | ((pixel << 24) & 0xFF000000);
+ }
+ p += glWidth - width;
+ }
+
+ } else {
+
+ // We have to convert ABGR into ARGB, so R and B must be swapped,
+ // A and G just brought back in.
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int pixel = intArray[p++];
+ tIntArray[t++] = ((pixel & 0xFF) << 16) |
+ ((pixel & 0xFF0000) >> 16) |
+ (pixel & 0xFF00FF00);
+
+ }
+ p += glWidth - width;
+ }
+
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////
+
+ // Create/delete texture.
+
+
+ /**
+ * Creates the opengl texture object.
+ * @param w int
+ * @param h int
+ */
+ protected void createTexture(int w, int h) {
+ deleteTexture(); // Just in the case this object is being re-initialized.
+
+ if (PGraphicsOpenGL2.npotTexSupported) {
+ glWidth = w;
+ glHeight = h;
+ } else {
+ glWidth = nextPowerOfTwo(w);
+ glHeight = nextPowerOfTwo(h);
+ }
+
+ if ((glWidth > PGraphicsOpenGL2.maxTextureSize) || (glHeight > PGraphicsOpenGL2.maxTextureSize)) {
+ glWidth = glHeight = 0;
+ throw new RuntimeException("Image width and height cannot be" +
+ " larger than " + PGraphicsOpenGL2.maxTextureSize +
+ " with this graphics card.");
+ }
+
+ usingMipmaps = glMinFilter == GL.GL_LINEAR_MIPMAP_LINEAR;
+
+ gl.glEnable(glTarget);
+ glID = pgl.createGLResource(PGraphicsOpenGL2.GL_TEXTURE_OBJECT);
+ gl.glBindTexture(glTarget, glID);
+ gl.glTexParameteri(glTarget, GL.GL_TEXTURE_MIN_FILTER, glMinFilter);
+ gl.glTexParameteri(glTarget, GL.GL_TEXTURE_MAG_FILTER, glMagFilter);
+ gl.glTexParameteri(glTarget, GL.GL_TEXTURE_WRAP_S, glWrapS);
+ gl.glTexParameteri(glTarget, GL.GL_TEXTURE_WRAP_T, glWrapT);
+
+ // This array is used to make sure that the texture doesn't contain any
+ // garbage.
+ int[] initArray = new int[glWidth * glHeight];
+ java.util.Arrays.fill(initArray, 0, glWidth * glHeight, 0x00000000);
+ gl.glTexImage2D(glTarget, 0, glFormat, glWidth, glHeight, 0, GL.GL_RGBA,
+ GL.GL_UNSIGNED_BYTE, IntBuffer.wrap(initArray));
+ gl.glBindTexture(glTarget, 0);
+ gl.glDisable(glTarget);
+
+ flippedX = false;
+ flippedY = false;
+
+ // If non-power-of-two textures are not supported, and the specified width or height
+ // is non-power-of-two, then glWidth (glHeight) will be greater than w (h) because it
+ // is chosen to be the next power of two, and this quotient will give the appropriate
+ // maximum texture coordinate value given this situation.
+ maxTexCoordU = (float)w / glWidth;
+ maxTexCoordV = (float)h / glHeight;
+ }
+
+
+ /**
+ * Deletes the opengl texture object.
+ */
+ protected void deleteTexture() {
+ if (glID != 0) {
+ pgl.deleteGLResource(glID, PGraphicsOpenGL2.GL_TEXTURE_OBJECT);
+ glID = 0;
+ }
+ }
+
+ // Copies source texture tex into this.
+ protected void copyTexels(PTexture tex, int x, int y, int w, int h, boolean scale) {
+ if (tex == null) {
+ throw new RuntimeException("PTexture: source texture is null");
+ }
+
+ if (tempFbo == null) {
+ tempFbo = new PFramebuffer(parent, glWidth, glHeight);
+ }
+
+ // This texture is the color (destination) buffer of the FBO.
+ tempFbo.setColorBuffer(this);
+ tempFbo.disableDepthTest();
+
+ // FBO copy:
+ pgl.pushFramebuffer();
+ pgl.setFramebuffer(tempFbo);
+ if (scale) {
+ // Rendering tex into "this", and scaling the source rectangle
+ // to cover the entire destination region.
+ pgl.drawTexture(tex, x, y, w, h, 0, 0, width, height);
+ } else {
+ // Rendering tex into "this" but without scaling so the contents
+ // of the source texture fall in the corresponding texels of the
+ // destination.
+ pgl.drawTexture(tex, x, y, w, h, x, y, w, h);
+ }
+ pgl.popFramebuffer();
+ }
+
+ protected void setTexels(int x, int y, int w, int h, int[] pix) {
+ setTexels(0, x, y, w, h, pix);
+ }
+
+ protected void setTexels(int level, int x, int y, int w, int h, int[] pix) {
+ gl.glTexSubImage2D(glTarget, 0, x, y, w, h, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, IntBuffer.wrap(pix));
+ }
+
+ protected void bind() {
+ gl.glEnable(glTarget);
+ gl.glBindTexture(glTarget, glID);
+ }
+
+ protected void unbind() {
+ gl.glEnable(glTarget);
+ gl.glBindTexture(glTarget, 0);
+ }
+
+ protected void copyObject(PTexture src) {
+ // The OpenGL texture of this object is replaced with the one from the source object,
+ // so we delete the former to avoid resource wasting.
+ deleteTexture();
+
+ width = src.width;
+ height = src.height;
+
+ parent = src.parent;
+ pgl = src.pgl;
+ gl = src.gl;
+
+ glID = src.glID;
+ glTarget = src.glTarget;
+ glFormat = src.glFormat;
+ glMinFilter = src.glMinFilter;
+ glMagFilter = src.glMagFilter;
+
+ glWidth= src.glWidth;
+ glHeight = src.glHeight;
+
+ usingMipmaps = src.usingMipmaps;
+ maxTexCoordU = src.maxTexCoordU;
+ maxTexCoordV = src.maxTexCoordV;
+
+ flippedX = src.flippedX;
+ flippedY = src.flippedY;
+ }
+
+ ///////////////////////////////////////////////////////////
+
+ // Parameter handling
+
+
+ public Parameters getParameters() {
+ Parameters res = new Parameters();
+
+ if ( glTarget == GL.GL_TEXTURE_2D ) {
+ res.target = TEXTURE2D;
+ }
+
+ if (glFormat == GL.GL_RGB) {
+ res.format = RGB;
+ } else if (glFormat == GL.GL_RGBA) {
+ res.format = ARGB;
+ } else if (glFormat == GL.GL_ALPHA) {
+ res.format = ALPHA;
+ }
+
+ if (glMinFilter == GL.GL_NEAREST) {
+ res.sampling = POINT;
+ } else if (glMinFilter == GL.GL_LINEAR) {
+ res.sampling = BILINEAR;
+ } else if (glMinFilter == GL.GL_LINEAR_MIPMAP_LINEAR) {
+ res.sampling = TRILINEAR;
+ }
+
+ if (glWrapS == GL.GL_CLAMP_TO_EDGE) {
+ res.wrapU = CLAMP;
+ } else if (glWrapS == GL.GL_REPEAT) {
+ res.wrapU = REPEAT;
+ }
+
+ if (glWrapT == GL.GL_CLAMP_TO_EDGE) {
+ res.wrapV = CLAMP;
+ } else if (glWrapT == GL.GL_REPEAT) {
+ res.wrapV = REPEAT;
+ }
+
+ return res;
+ }
+
+
+ /**
+ * Sets texture target and internal format according to the target and type specified.
+ * @param target int
+ * @param params GLTextureParameters
+ */
+ protected void setParameters(Parameters params) {
+ if (params.target == TEXTURE2D) {
+ glTarget = GL.GL_TEXTURE_2D;
+ } else {
+ throw new RuntimeException("GTexture: Unknown texture target");
+ }
+
+ if (params.format == RGB) {
+ glFormat = GL.GL_RGB;
+ } else if (params.format == ARGB) {
+ glFormat = GL.GL_RGBA;
+ } else if (params.format == ALPHA) {
+ glFormat = GL.GL_ALPHA;
+ } else {
+ throw new RuntimeException("GTexture: Unknown texture format");
+ }
+
+ if (params.sampling == POINT) {
+ glMagFilter = GL.GL_NEAREST;
+ glMinFilter = GL.GL_NEAREST;
+ } else if (params.sampling == BILINEAR) {
+ glMagFilter = GL.GL_LINEAR;
+ glMinFilter = GL.GL_LINEAR;
+ } else if (params.sampling == TRILINEAR) {
+ glMagFilter = GL.GL_LINEAR;
+ glMinFilter = GL.GL_LINEAR_MIPMAP_LINEAR;
+ } else {
+ throw new RuntimeException("GTexture: Unknown texture filtering mode");
+ }
+
+ if (params.wrapU == CLAMP) {
+ glWrapS = GL.GL_CLAMP_TO_EDGE;
+ } else if (params.wrapU == REPEAT) {
+ glWrapS = GL.GL_REPEAT;
+ } else {
+ throw new RuntimeException("GTexture: Unknown wrapping mode");
+ }
+
+ if (params.wrapV == CLAMP) {
+ glWrapT = GL.GL_CLAMP_TO_EDGE;
+ } else if (params.wrapV == REPEAT) {
+ glWrapT = GL.GL_REPEAT;
+ } else {
+ throw new RuntimeException("GTexture: Unknown wrapping mode");
+ }
+ }
+
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ // Parameters object
+
+
+ static public Parameters newParameters() {
+ return new Parameters();
+ }
+
+
+ static public Parameters newParameters(int format) {
+ return new Parameters(format);
+ }
+
+
+ static public Parameters newParameters(int format, int sampling) {
+ return new Parameters(format, sampling);
+ }
+
+
+ static public Parameters newParameters(Parameters params) {
+ return new Parameters(params);
+ }
+
+
+ /**
+ * This class stores the parameters for a texture: target, internal format, minimization filter
+ * and magnification filter.
+ */
+ static public class Parameters extends PParameters {
+ /**
+ * Texture target.
+ */
+ public int target;
+
+ /**
+ * Texture internal format.
+ */
+ public int format;
+
+ /**
+ * Texture filtering (POINT, BILINEAR or TRILINEAR).
+ */
+ public int sampling;
+
+ /**
+ * Wrapping mode along U.
+ */
+ public int wrapU;
+
+ /**
+ * Wrapping mode along V.
+ */
+ public int wrapV;
+
+ /**
+ * Creates an instance of GLTextureParameters, setting all the parameters to default values.
+ */
+ public Parameters() {
+ this.target = TEXTURE2D;
+ this.format = ARGB;
+ this.sampling = BILINEAR;
+ this.wrapU = CLAMP;
+ this.wrapV = CLAMP;
+ }
+
+ public Parameters(int format) {
+ this.target = TEXTURE2D;
+ this.format = format;
+ this.sampling = BILINEAR;
+ this.wrapU = CLAMP;
+ this.wrapV = CLAMP;
+ }
+
+ public Parameters(int format, int sampling) {
+ this.target = TEXTURE2D;
+ this.format = format;
+ this.sampling = sampling;
+ this.wrapU = CLAMP;
+ this.wrapV = CLAMP;
+ }
+
+ public Parameters(Parameters src) {
+ this.target = src.target;
+ this.format = src.format;
+ this.sampling = src.sampling;
+ this.wrapU = src.wrapU;
+ this.wrapV = src.wrapV;
+ }
+
+ public void set(int format) {
+ this.format = format;
+ }
+
+ public void set(int format, int sampling) {
+ this.format = format;
+ this.sampling = sampling;
+ }
+
+ public void set(Parameters src) {
+ this.target = src.target;
+ this.format = src.format;
+ this.sampling = src.sampling;
+ this.wrapU = src.wrapU;
+ this.wrapV = src.wrapV;
+ }
+ }
+}