/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2004-08 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 as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. 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.opengl; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.GraphicsDevice; import java.awt.RenderingHints; import java.awt.Shape; import java.awt.font.FontRenderContext; import java.awt.font.GlyphMetrics; import java.awt.font.GlyphVector; import java.awt.geom.PathIterator; import java.nio.ByteOrder; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.Arrays; import java.util.IdentityHashMap; import javax.media.nativewindow.AbstractGraphicsScreen; import javax.media.nativewindow.Capabilities; import javax.media.nativewindow.GraphicsConfigurationFactory; import javax.media.nativewindow.NativeWindowFactory; import javax.media.nativewindow.awt.AWTGraphicsConfiguration; import javax.media.nativewindow.awt.AWTGraphicsDevice; import javax.media.nativewindow.awt.AWTGraphicsScreen; import javax.media.opengl.DefaultGLCapabilitiesChooser; import javax.media.opengl.GL; import javax.media.opengl.GL2; import javax.media.opengl.GLCapabilities; import javax.media.opengl.GLContext; import javax.media.opengl.GLDrawable; import javax.media.opengl.GLDrawableFactory; import javax.media.opengl.GLException; import javax.media.opengl.GLProfile; import javax.media.opengl.glu.GLU; import javax.media.opengl.glu.GLUtessellator; import javax.media.opengl.glu.GLUtessellatorCallbackAdapter; import processing.core.PApplet; import processing.core.PFont; import processing.core.PGraphics; import processing.core.PGraphics3D; import processing.core.PImage; import com.jogamp.common.nio.Buffers; import com.jogamp.opengl.impl.GLContextImpl; /** * Implementation of the PGraphics API that employs OpenGL2 rendering via JOGL. * <p/> * JOGL2 requires Java 1.4 or higher, so there are no restrictions on this * code to be compatible with Java 1.1 or Java 1.3. * <p/> * This code relies on PGraphics3D for all lighting and transformations. * Meaning that translate(), rotate(), and any lighting will be done in * PGraphics3D, and OpenGL2 is only used to blit lines and triangles as fast * as it possibly can. * <p/> * For this reason, OpenGL2 may not be accelerated as far as it could be, * but I don't have the time to maintain two separate versions of the * renderer. My development time must always be focused on implementation * and covering features first, and optimizing later. * <p/> * Further, the difference may be negligible, as the primary slowdown * in Java is moving pixels (i.e. a large frame buffer is nearly impossible * because Java just can't do a MemoryImageSource at screen resolution) * and the overhead from JNI tends to be significant. In the latter case, * we may even save time in some cases where a large number of calls to * OpenGL2 would otherwise be used, but that's probably a stretch. * <p/> * The code is also very messy, while features are being added and * removed rapidly as we head towards 1.0. Things got particularly ugly * as we approached beta while both Simon and I were working on it. * Relax, we'll get it fixed up later. * <p/> * When exporting applets, the JOGL2 Applet Launcher is used. More information * about the launcher can be found at its <A HREF="http://download.java.net/media/jogl/builds/nightly/javadoc_public/com/sun/opengl/util/JOGLAppletLauncher.html">documentation page</A>. */ public class PGraphicsOpenGL extends PGraphics3D { protected GLDrawable drawable; // the rendering 'surface' protected GLContext context; // the rendering context (holds rendering state info) public GL2 gl; public GLU glu; //public GLCanvas canvas; //protected FloatBuffer projectionFloatBuffer; protected float[] projectionFloats; protected GLUtessellator tobj; protected TessCallback tessCallback; /// Buffer to hold light values before they're sent to OpenGL //protected FloatBuffer lightBuffer; protected float[] lightArray = new float[] { 1, 1, 1 }; static int maxTextureSize; int[] textureDeleteQueue = new int[10]; int textureDeleteQueueCount = 0; /// Used to hold color values to be sent to OpenGL //protected FloatBuffer colorBuffer; protected float[] colorBuffer; /// Used to store empty values to be passed when a light has no ambient value protected FloatBuffer zeroBuffer; /// IntBuffer to go with the pixels[] array protected IntBuffer pixelBuffer; /** * Set to true if the host system is big endian (PowerPC, MIPS, SPARC), * false if little endian (x86 Intel for Mac or PC). */ static public boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; public PGraphicsOpenGL() { glu = new GLU(); tobj = glu.gluNewTess(); // unfortunately glu.gluDeleteTess(tobj); is never called //glu.gluTessProperty(tobj, GLU.GLU_TESS_WINDING_RULE, // GLU.GLU_TESS_WINDING_NONZERO); //glu.gluTessProperty(tobj, GLU.GLU_TESS_WINDING_RULE, // GLU.GLU_TESS_WINDING_POSITIVE); //GLU.GLU_TESS_WINDING_ODD); //glu.gluTessProperty(tobj, GLU.GLU_TESS_BOUNDARY_ONLY, // GL.GL_TRUE); tessCallback = new TessCallback(); glu.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, tessCallback); glu.gluTessCallback(tobj, GLU.GLU_TESS_END, tessCallback); glu.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, tessCallback); glu.gluTessCallback(tobj, GLU.GLU_TESS_COMBINE, tessCallback); glu.gluTessCallback(tobj, GLU.GLU_TESS_ERROR, tessCallback); // lightBuffer = BufferUtil.newFloatBuffer(4); // lightBuffer.put(3, 1.0f); // lightBuffer.rewind(); } //public void setParent(PApplet parent) // PGraphics //public void setPrimary(boolean primary) // PGraphics //public void setPath(String path) // PGraphics //public void setSize(int iwidth, int iheight) // PGraphics3D /** * Called by resize(), this handles creating the actual GLCanvas the * first time around, or simply resizing it on subsequent calls. * There is no pixel array to allocate for an OpenGL2 canvas * because OpenGL's pixel buffer is all handled internally. */ protected void allocate() { if (true) { return; } if (context == null) { // System.out.println("PGraphicsOpenGL.allocate() for " + width + " " + height); // new Exception().printStackTrace(System.out); // If OpenGL2 2X or 4X smoothing is enabled, setup caps object for them GLProfile pf = GLProfile.get(GLProfile.GL2); GLCapabilities capabilities = new GLCapabilities(pf); // Starting in release 0158, OpenGL2 smoothing is always enabled if (true || !hints[DISABLE_OPENGL_2X_SMOOTH]) { capabilities.setSampleBuffers(true); capabilities.setNumSamples(2); } else if (hints[ENABLE_OPENGL_4X_SMOOTH]) { capabilities.setSampleBuffers(true); capabilities.setNumSamples(4); } // get a rendering surface and a context for this canvas GLDrawableFactory factory = GLDrawableFactory.getFactory(pf); AWTGraphicsConfiguration awtConfig; GraphicsDevice device = parent.getGraphicsConfiguration() .getDevice(); AbstractGraphicsScreen aScreen = AWTGraphicsScreen .createScreenDevice(device); awtConfig = (AWTGraphicsConfiguration) GraphicsConfigurationFactory .getFactory(AWTGraphicsDevice.class) .chooseGraphicsConfiguration(capabilities, new DefaultGLCapabilitiesChooser() { public int chooseCapabilities( Capabilities arg0, Capabilities[] arg1, int arg2) { System.out.println(Arrays.toString(arg1) + " " + arg2); int sup = super.chooseCapabilities(arg0, arg1, arg2); sup = -1; System.out.println("Passing up " + sup); return sup; } }, aScreen); if (null == awtConfig) { throw new GLException("Error: AWTGraphicsConfiguration is null"); } System.out.println(parent); drawable = factory.createGLDrawable(NativeWindowFactory .getNativeWindow(parent, awtConfig)); context = (GLContextImpl) drawable.createContext(null); context.setSynchronized(true); drawable.setRealized(true); // need to get proper opengl context since will be needed below gl = context.getGL().getGL2(); /* GLCanvas gc = new GLCanvas(capabilities,parent); gl = gc.addNotify().getGL2(); */ // Flag defaults to be reset on the next trip into beginDraw(). settingsInited = false; } else { background(0); } } //public void dispose() // PGraphics //////////////////////////////////////////////////////////// /** * Get the current context, for use by libraries that need to talk to it. */ public GLContext getContext() { return context; } /** * Make the OpenGL2 rendering context current for this thread. */ protected void detainContext() { try { while (context.makeCurrent() == GLContext.CONTEXT_NOT_CURRENT) { // System.out.println("Context not yet current..."); // new Exception().printStackTrace(System.out); // Thread.sleep(1000); Thread.sleep(10); } } catch (InterruptedException e) { e.printStackTrace(); } } /** * Release the context, otherwise the AWT lock on X11 will not be released */ protected void releaseContext() { context.release(); } /** * OpenGL2 cannot draw until a proper native peer is available, so this * returns the value of PApplet.isDisplayable() (inherited from Component). */ public boolean canDraw() { return gl != null; //parent.isDisplayable(); } public void beginDraw() { /* //if (!parent.isDisplayable()) return; // When using an offscreen buffer, the drawable instance will be null. // The offscreen buffer uses the drawing context of the main PApplet. if (drawable != null) { // Call setRealized() after addNotify() has been called drawable.setRealized(parent.isDisplayable()); //System.out.println("OpenGL2 beginDraw() setting realized " + parent.isDisplayable()); if (parent.isDisplayable()) { //System.out.println(" we'll realize it alright"); drawable.setRealized(true); } else { //System.out.println(" not yet ready to be realized"); return; // Should have called canDraw() anyway } detainContext(); } // On the first frame that's guaranteed to be on screen, // and the component valid and all that, ask for focus. // if ((parent != null) && parent.frameCount == 1) { // canvas.requestFocus(); // } * */ super.beginDraw(); report("top beginDraw()"); gl.glDisable(GL2.GL_LIGHTING); for (int i = 0; i < MAX_LIGHTS; i++) { gl.glDisable(GL2.GL_LIGHT0 + i); } gl.glMatrixMode(GL2.GL_PROJECTION); if (projectionFloats == null) { projectionFloats = new float[] { projection.m00, projection.m10, projection.m20, projection.m30, projection.m01, projection.m11, projection.m21, projection.m31, projection.m02, projection.m12, projection.m22, projection.m32, projection.m03, projection.m13, projection.m23, projection.m33 }; } else { projectionFloats[0] = projection.m00; projectionFloats[1] = projection.m10; projectionFloats[2] = projection.m20; projectionFloats[3] = projection.m30; projectionFloats[4] = projection.m01; projectionFloats[5] = projection.m11; projectionFloats[6] = projection.m21; projectionFloats[7] = projection.m31; projectionFloats[8] = projection.m02; projectionFloats[9] = projection.m12; projectionFloats[10] = projection.m22; projectionFloats[11] = projection.m32; projectionFloats[12] = projection.m03; projectionFloats[13] = projection.m13; projectionFloats[14] = projection.m23; projectionFloats[15] = projection.m33; } //projection.print(); gl.glLoadMatrixf(projectionFloats, 0); gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glLoadIdentity(); // Flip Y-axis to make y count from 0 downwards gl.glScalef(1, -1, 1); // these are necessary for alpha (i.e. fonts) to work gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA); // this is necessary for 3D drawing if (hints[DISABLE_DEPTH_TEST]) { gl.glDisable(GL.GL_DEPTH_TEST); } else { gl.glEnable(GL.GL_DEPTH_TEST); } // use <= since that's what processing.core does gl.glDepthFunc(GL.GL_LEQUAL); // because y is flipped gl.glFrontFace(GL.GL_CW); // coloured stuff gl.glEnable(GL2.GL_COLOR_MATERIAL); gl.glColorMaterial(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT_AND_DIFFUSE); gl.glColorMaterial(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR); // these tend to make life easier // (but sometimes at the expense of a little speed) // Not using them right now because we're doing our own lighting. //gl.glEnable(GL.GL_NORMALIZE); //gl.glEnable(GL.GL_AUTO_NORMAL); // I think this is OpenGL2 1.2 only //gl.glEnable(GL.GL_RESCALE_NORMAL); //gl.GlLightModeli(GL.GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); report("bot beginDraw()"); // are there other things to do here? //System.out.println("beginDraw() stop error " + PApplet.hex(gl.glGetError())); gl.glViewport(0, 0, width, height); } public void endDraw() { //System.out.println("endDraw() error " + PApplet.hex(gl.glGetError())); report("top endDraw()"); if (hints[ENABLE_DEPTH_SORT]) { flush(); } /* if (drawable != null) { drawable.swapBuffers(); } //insideDraw = false; report("bot endDraw()"); if (drawable != null) { releaseContext(); } */ } private float ctm[]; // this would also need to set up the lighting.. ? public GL2 beginGL() { //beginDraw(); // frame will have already started gl.glPushMatrix(); // load p5 modelview into the opengl modelview if (ctm == null) ctm = new float[16]; ctm[0] = modelview.m00; ctm[1] = modelview.m10; ctm[2] = modelview.m20; ctm[3] = modelview.m30; ctm[4] = modelview.m01; ctm[5] = modelview.m11; ctm[6] = modelview.m21; ctm[7] = modelview.m31; ctm[8] = modelview.m02; ctm[9] = modelview.m12; ctm[10] = modelview.m22; ctm[11] = modelview.m32; ctm[12] = modelview.m03; ctm[13] = modelview.m13; ctm[14] = modelview.m23; ctm[15] = modelview.m33; // apply this modelview and get to work gl.glMultMatrixf(ctm, 0); return gl; } public void endGL() { // remove the p5 modelview from opengl gl.glPopMatrix(); } //////////////////////////////////////////////////////////// // SETTINGS // checkSettings, defaultSettings, reapplySettings in PGraphics //////////////////////////////////////////////////////////// // HINTS public void hint(int which) { // make note of whether these are set, if they are, // then will prevent the new renderer exception from being thrown. boolean opengl2X = !hints[DISABLE_OPENGL_2X_SMOOTH]; boolean opengl4X = hints[ENABLE_OPENGL_4X_SMOOTH]; super.hint(which); if (which == DISABLE_DEPTH_TEST) { gl.glDisable(GL.GL_DEPTH_TEST); gl.glClear(GL.GL_DEPTH_BUFFER_BIT); } else if (which == ENABLE_DEPTH_TEST) { gl.glEnable(GL.GL_DEPTH_TEST); } else if (which == DISABLE_OPENGL_2X_SMOOTH) { if (opengl2X) { releaseContext(); context.destroy(); context = null; allocate(); throw new PApplet.RendererChangeException(); } } else if (which == ENABLE_OPENGL_2X_SMOOTH) { // do nothing, this is the default in release 0158 and later } else if (which == ENABLE_OPENGL_4X_SMOOTH) { if (!opengl4X) { releaseContext(); context.destroy(); context = null; allocate(); throw new PApplet.RendererChangeException(); } } } ////////////////////////////////////////////////////////////// // VERTEX SHAPES // All picked up from either PGraphics or PGraphics3D //public void beginShape() //public void beginShape(int kind) //public void edge(boolean e) //public void normal(float nx, float ny, float nz) //public void textureMode(int mode) //public void texture(PImage image) //public void vertex(float x, float y) //public void vertex(float x, float y, float z) //public void vertex(float x, float y, float u, float v) //public void vertex(float x, float y, float z, float u, float v) //protected void vertexTexture(float u, float v); //public void breakShape() //public void endShape() //public void endShape(int mode) protected void endShapeLighting(boolean lights) { super.endShapeLighting(lights); // For now do our own lighting--sum the specular and diffuse light colors if (lights) { for (int i = shapeFirst; i < shapeLast; i++) { float v[] = vertices[i]; v[R] = clamp(v[R] + v[SPR]); v[G] = clamp(v[G] + v[SPG]); v[B] = clamp(v[B] + v[SPB]); } } } ////////////////////////////////////////////////////////////// // BEZIER CURVE VERTICES // All picked up from either PGraphics or PGraphics3D, however // a faster version that made use of OpenGL's evaluator methods // would be a nice improvement. //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 // Like bezier, these could be implemented using an OpenGL2 evaluator. //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) { float sw = vertices[lines[start][VERTEX1]][SW]; if (sw > 0) { gl.glPointSize(sw); // can only be set outside glBegin/glEnd gl.glBegin(GL.GL_POINTS); for (int i = start; i < stop; i++) { float[] a = vertices[points[i][VERTEX1]]; gl.glColor4f(a[SR], a[SG], a[SB], a[SA]); gl.glVertex3f(a[VX], a[VY], a[VZ]); } gl.glEnd(); } } //protected void rawPoints(int start, int stop) // PGraphics3D ////////////////////////////////////////////////////////////// // LINES (override from P3D) //protected final void addLineBreak() // PGraphics3D /** * Add this line, but disable clipping because GL2 will handle it. */ protected void addLine(int a, int b) { addLineWithoutClip(a, b); } //protected final void addLineWithClip(int a, int b) //protected final void addLineWithoutClip(int a, int b) /** * 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"); //int i = 0; boolean lastWasLine = false; boolean hasPendingEnd = false; for (int j = 0; j < pathCount; 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) { // glLineWidth has to occur outside glBegin/glEnd if (pathLength[j]!=1){ if (hasPendingEnd){ gl.glEnd(); } gl.glLineWidth(sw); gl.glBegin(GL.GL_LINE_STRIP); hasPendingEnd = true; lastWasLine = false; } else { if (!lastWasLine){ if (hasPendingEnd){ gl.glEnd(); } gl.glLineWidth(sw); gl.glBegin(GL.GL_LINES); hasPendingEnd = true; } lastWasLine = true; } // always draw a first point float a[] = vertices[lines[i][VERTEX1]]; gl.glColor4f(a[SR], a[SG], a[SB], a[SA]); gl.glVertex3f(a[VX], a[VY], a[VZ]); // on this and subsequent lines, only draw the second point //System.out.println(pathLength[j]); for (int k = 0; k < pathLength[j]; k++) { float b[] = vertices[lines[i][VERTEX2]]; gl.glColor4f(b[SR], b[SG], b[SB], b[SA]); //gl.glEdgeFlag(a[EDGE] == 1); gl.glVertex3f(b[VX], b[VY], b[VZ]); i++; } } } if (hasPendingEnd){ gl.glEnd(); hasPendingEnd = false; } report("render_lines out"); } //protected void rawLines(int start, int stop) ////////////////////////////////////////////////////////////// // TRIANGLES /** * Add the triangle, but disable clipping because GL2 will handle it. */ protected void addTriangle(int a, int b, int c) { addTriangleWithoutClip(a, b, c); } protected void renderTriangles(int start, int stop) { report("render_triangles in"); for (int i = start; i < stop; i++) { float a[] = vertices[triangles[i][VERTEX1]]; float b[] = vertices[triangles[i][VERTEX2]]; float c[] = vertices[triangles[i][VERTEX3]]; // This is only true when not textured. // We really should pass specular straight through to triangle rendering. float ar = clamp(triangleColors[i][0][TRI_DIFFUSE_R] + triangleColors[i][0][TRI_SPECULAR_R]); float ag = clamp(triangleColors[i][0][TRI_DIFFUSE_G] + triangleColors[i][0][TRI_SPECULAR_G]); float ab = clamp(triangleColors[i][0][TRI_DIFFUSE_B] + triangleColors[i][0][TRI_SPECULAR_B]); float br = clamp(triangleColors[i][1][TRI_DIFFUSE_R] + triangleColors[i][1][TRI_SPECULAR_R]); float bg = clamp(triangleColors[i][1][TRI_DIFFUSE_G] + triangleColors[i][1][TRI_SPECULAR_G]); float bb = clamp(triangleColors[i][1][TRI_DIFFUSE_B] + triangleColors[i][1][TRI_SPECULAR_B]); float cr = clamp(triangleColors[i][2][TRI_DIFFUSE_R] + triangleColors[i][2][TRI_SPECULAR_R]); float cg = clamp(triangleColors[i][2][TRI_DIFFUSE_G] + triangleColors[i][2][TRI_SPECULAR_G]); float cb = clamp(triangleColors[i][2][TRI_DIFFUSE_B] + triangleColors[i][2][TRI_SPECULAR_B]); int textureIndex = triangles[i][TEXTURE_INDEX]; if (textureIndex != -1) { report("before enable"); gl.glEnable(GL.GL_TEXTURE_2D); report("after enable"); report("before bind"); PImage texture = textures[textureIndex]; bindTexture(texture); report("after bind"); ImageCache cash = (ImageCache) texture.getCache(this); float uscale = (float) texture.width / (float) cash.twidth; float vscale = (float) texture.height / (float) cash.theight; gl.glBegin(GL.GL_TRIANGLES); //System.out.println(a[U] + " " + a[V] + " " + uscale + " " + vscale); //System.out.println(ar + " " + ag + " " + ab + " " + a[A]); //ar = ag = ab = 1; gl.glColor4f(ar, ag, ab, a[A]); gl.glTexCoord2f(a[U] * uscale, a[V] * vscale); gl.glNormal3f(a[NX], a[NY], a[NZ]); gl.glEdgeFlag(a[EDGE] == 1); gl.glVertex3f(a[VX], a[VY], a[VZ]); gl.glColor4f(br, bg, bb, b[A]); gl.glTexCoord2f(b[U] * uscale, b[V] * vscale); gl.glNormal3f(b[NX], b[NY], b[NZ]); gl.glEdgeFlag(a[EDGE] == 1); gl.glVertex3f(b[VX], b[VY], b[VZ]); gl.glColor4f(cr, cg, cb, c[A]); gl.glTexCoord2f(c[U] * uscale, c[V] * vscale); gl.glNormal3f(c[NX], c[NY], c[NZ]); gl.glEdgeFlag(a[EDGE] == 1); gl.glVertex3f(c[VX], c[VY], c[VZ]); gl.glEnd(); report("non-binding 6"); gl.glDisable(GL.GL_TEXTURE_2D); } else { // no texture gl.glBegin(GL.GL_TRIANGLES); gl.glColor4f(ar, ag, ab, a[A]); gl.glNormal3f(a[NX], a[NY], a[NZ]); gl.glEdgeFlag(a[EDGE] == 1); gl.glVertex3f(a[VX], a[VY], a[VZ]); gl.glColor4f(br, bg, bb, b[A]); gl.glNormal3f(b[NX], b[NY], b[NZ]); gl.glEdgeFlag(a[EDGE] == 1); gl.glVertex3f(b[VX], b[VY], b[VZ]); gl.glColor4f(cr, cg, cb, c[A]); gl.glNormal3f(c[NX], c[NY], c[NZ]); gl.glEdgeFlag(a[EDGE] == 1); gl.glVertex3f(c[VX], c[VY], c[VZ]); gl.glEnd(); } } report("render_triangles out"); } //protected void rawTriangles(int start, int stop) // PGraphics3D public boolean MAKE_MIPMAPS = false; public int[] bindTexture(PImage texture) { ImageCache cash = (ImageCache) texture.getCache(this); // as in johnny if (cash == null) { cash = new ImageCache(); texture.setCache(this, cash); texture.setModified(true); } if (texture.isModified()) { //System.out.println("texture modified"); // TODO make this more efficient and just update a sub-part // based on mx1 et al, also use gl function to update // only a sub-portion of the image. cash.rebind(texture); // clear the modified flag texture.setModified(false); } else { gl.glBindTexture(GL.GL_TEXTURE_2D, cash.tindex); } return new int[] { cash.twidth, cash.theight }; } private IdentityHashMap<PImage, Integer> unmodifiable = new IdentityHashMap<PImage, Integer>(); public void setUnmodifiablePImage(PImage g) { unmodifiable.put(g, 0); } public void checkImageSizeValid(PImage source) { // bit shifting this might be more efficient int width2 = nextPowerOfTwo(source.width); //(int) Math.pow(2, Math.ceil(Math.log(source.width) / Math.log(2))); int height2 = nextPowerOfTwo(source.height); //(int) Math.pow(2, Math.ceil(Math.log(source.height) / Math.log(2))); // use glGetIntegerv with the argument GL_MAX_TEXTURE_SIZE // to figure out min/max texture sizes if (maxTextureSize == 0) { int maxSize[] = new int[1]; gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, maxSize, 0); maxTextureSize = maxSize[0]; //System.out.println("max texture size is " + maxTextureSize); } if ((width2 > maxTextureSize) || (height2 > maxTextureSize)) { throw new RuntimeException("Image width and height cannot be" + " larger than " + maxTextureSize + " with your graphics card."); } } protected class ImageCache { int tindex = -1; // not yet ready int tpixels[]; IntBuffer tbuffer; public int twidth, theight; int[] tp; /** * Delete any texture memory that had been allocated. * Added for 0125 to deal with memory problems reported in Bug #150. */ protected void finalize() { if (textureDeleteQueue.length == textureDeleteQueueCount) { textureDeleteQueue = (int[]) PApplet.expand(textureDeleteQueue); } if (tindex != -1) { textureDeleteQueue[textureDeleteQueueCount++] = tindex; } } /** * Generate a texture ID and do the necessary bitshifting for the image. */ public void rebind(PImage source) { if (textureDeleteQueueCount != 0) { //gl.glDeleteTextures(1, new int[] { tindex }, 0); gl.glDeleteTextures(textureDeleteQueueCount, textureDeleteQueue, 0); textureDeleteQueueCount = 0; } //System.out.println("rebinding texture for " + source); boolean hasShallowCopy = false; if (tindex != -1) { // free up the old memory //gl.glDeleteTextures(1, new int[] { tindex }, 0); hasShallowCopy = true; } else { // generate a new texture number to bind to int[] tmp = new int[1]; gl.glGenTextures(1, tmp, 0); tindex = tmp[0]; } //System.out.println("got index " + tindex); // bit shifting this might be more efficient int width2 = nextPowerOfTwo(source.width); //(int) Math.pow(2, Math.ceil(Math.log(source.width) / Math.log(2))); int height2 = nextPowerOfTwo(source.height); //(int) Math.pow(2, Math.ceil(Math.log(source.height) / Math.log(2))); // use glGetIntegerv with the argument GL_MAX_TEXTURE_SIZE // to figure out min/max texture sizes if (maxTextureSize == 0) { int maxSize[] = new int[1]; gl.glGetIntegerv(GL.GL_MAX_TEXTURE_SIZE, maxSize, 0); maxTextureSize = maxSize[0]; //System.out.println("max texture size is " + maxTextureSize); } if ((width2 > maxTextureSize) || (height2 > maxTextureSize)) { throw new RuntimeException("Image width and height cannot be" + " larger than " + maxTextureSize + " with your graphics card."); } if ((width2 > twidth) || (height2 > theight)) { // either twidth/theight are zero, or size has changed tpixels = null; } if (tpixels == null) { twidth = width2; theight = height2; tpixels = new int[twidth * theight]; tbuffer = Buffers.newDirectIntBuffer(twidth * theight); } // copy image data into the texture int p = 0; int t = 0; if (BIG_ENDIAN) { switch (source.format) { case ALPHA: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { tpixels[t++] = 0xFFFFFF00 | source.pixels[p++]; } t += twidth - source.width; } break; case RGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; tpixels[t++] = (pixel << 8) | 0xff; } t += twidth - source.width; } break; case ARGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; tpixels[t++] = (pixel << 8) | ((pixel >> 24) & 0xff); } t += twidth - source.width; } 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 (source.format) { case ALPHA: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { tpixels[t++] = (source.pixels[p++] << 24) | 0x00FFFFFF; } t += twidth - source.width; } break; case RGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; // needs to be ABGR, stored in memory xRGB // so R and B must be swapped, and the x just made FF tpixels[t++] = 0xff000000 | // force opacity for good measure ((pixel & 0xFF) << 16) | ((pixel & 0xFF0000) >> 16) | (pixel & 0x0000FF00); } t += twidth - source.width; } break; case ARGB: for (int y = 0; y < source.height; y++) { for (int x = 0; x < source.width; x++) { int pixel = source.pixels[p++]; // needs to be ABGR stored in memory ARGB // so R and B must be swapped, A and G just brought back in tpixels[t++] = ((pixel & 0xFF) << 16) | ((pixel & 0xFF0000) >> 16) | (pixel & 0xFF00FF00); } t += twidth - source.width; } break; } } tbuffer.put(tpixels); tbuffer.rewind(); // gl.glBindTexture(GL.GL_TEXTURE_2D, tindex); gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); //gl.glPixelStorei(GL.GL_UNPACK_SWAP_BYTES, 0); if (!hasShallowCopy) { gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, 4, twidth, theight, //0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, cash.tpixels); 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, tbuffer); } else { gl.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0, twidth, theight, //0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, cash.tpixels); GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, tbuffer); } int filter = GL.GL_NEAREST; if (source.blit_resize_smooth) { filter = GL.GL_LINEAR; } gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, filter); gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, filter); // if (MAKE_MIPMAPS) { /* int err = glu.gluBuild2DMipmaps(GL.GL_TEXTURE_2D, 4, twidth, theight, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, tbuffer); //System.out.println("mipmap: " + err); // The MAG_FILTER should only be GL_LINEAR or GL_NEAREST. // Some cards are OK with LINEAR_MIPMAP_LINEAR, but not the // Radeon 9700, which is in all the PB G4s.. Not sure if this // is an OpenGL2 version thing, tho it makes sense MIN_FILTER // is the only one that uses mipmapping. gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR_MIPMAP_LINEAR); */ gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR); } // gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP); // gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP); gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT); gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT); // gl.glTexEnvf(GL2.GL_TEXTURE_ENV, GL2.GL_TEXTURE_ENV_MODE, GL2.GL_MODULATE); if (unmodifiable.containsKey(source)) { unmodifiable.remove(source); source.pixels = null; } } } private int nextPowerOfTwo(int val) { int ret = 1; while (ret < val) { ret <<= 1; } return ret; } ////////////////////////////////////////////////////////////// // 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) //public void ellipse(float a, float b, float c, float d) /* boolean ellipseInited; int ellipseFillList; int ellipseStrokeList; protected void ellipseImpl(float x1, float y1, float w, float h) { float hradius = w / 2f; float vradius = h / 2f; float centerX = x1 + hradius; float centerY = y1 + vradius; // adapt accuracy to radii used w/ a minimum of 4 segments [toxi] // now uses current scale factors to determine "real" transformed radius //int cAccuracy = (int)(4+Math.sqrt(hradius*abs(m00)+vradius*abs(m11))*2); //int cAccuracy = (int)(4+Math.sqrt(hradius+vradius)*2); // notched this up to *3 instead of *2 because things were // looking a little rough, i.e. the calculate->arctangent example [fry] // also removed the m00 and m11 because those were causing weirdness // need an actual measure of magnitude in there [fry] int accuracy = (int)(4+Math.sqrt(hradius+vradius)*3); //System.out.println("accuracy is " + accuracy); //accuracy = 5; // [toxi031031] adapted to use new lookup tables float inc = (float)SINCOS_LENGTH / accuracy; float val = 0; if (fill) { boolean savedStroke = stroke; stroke = false; beginShape(TRIANGLE_FAN); normal(0, 0, 1); vertex(centerX, centerY); for (int i = 0; i < accuracy; i++) { vertex(centerX + cosLUT[(int) val] * hradius, centerY + sinLUT[(int) val] * vradius); val += inc; } // back to the beginning vertex(centerX + cosLUT[0] * hradius, centerY + sinLUT[0] * vradius); endShape(); stroke = savedStroke; } if (stroke) { boolean savedFill = fill; fill = false; val = 0; beginShape(); //LINE_LOOP); for (int i = 0; i < accuracy; i++) { vertex(centerX + cosLUT[(int) val] * hradius, centerY + sinLUT[(int) val] * vradius); val += inc; } endShape(CLOSE); fill = savedFill; } } */ /* pgl.beginGL(); //PGraphics gr = PApplet.this.g; //GL2 gl = ((PGraphicsOpenGL).gr).beginGL(); if (!ellipseInited) { ellipseList = gl.glGenLists(1); gl.glNewList(ellipseList, GL.GL_COMPILE); gl.glBegin(GL.GL_LINE_LOOP); int seg = 15; float segf = 15; for (int i = 0; i < seg; i++) { float theta = TWO_PI * (float)i / segf; gl.glVertex2f(cos(theta), sin(theta)); } gl.glEnd(); gl.glEndList(); ellipseInited = true; } for (int i=1; i<numSegments-1; i++) { gl.glPushMatrix(); gl.glTranslatef(x[i], y[i], z); float r = w[i]/2f; gl.glScalef(r, r, r); gl.glColor4f(1, 1, 1, 150.0/255.0); gl.glCallList(ellipseList); gl.glScalef(0.5, 0.5, 0.5); gl.glColor4f(1, 1, 1, 50.0/255.0); gl.glCallList(ellipseList); gl.glPopMatrix(); } pgl.endGL(); */ //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 P3D overrides box to turn on triangle culling, but that's a waste // for OpenGL. Also could just use the cube method from GL2 or GLUT. //public void box(float size) //public void box(float w, float h, float d) // P3D ////////////////////////////////////////////////////////////// // SPHERE // TODO P3D overrides sphere to turn on triangle culling, but that's a waste // for OpenGL. Also could just use the cube method from GL2 or GLUT. //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) ////////////////////////////////////////////////////////////// // SMOOTH public void smooth() { smooth = true; if (hints[DISABLE_OPENGL_2X_SMOOTH]) { //gl.glEnable(GL.GL_MULTISAMPLE); gl.glEnable(GL2.GL_POINT_SMOOTH); gl.glEnable(GL.GL_LINE_SMOOTH); gl.glEnable(GL2.GL_POLYGON_SMOOTH); } } public void noSmooth() { smooth = false; if (hints[DISABLE_OPENGL_2X_SMOOTH]) { //gl.glDisable(GL.GL_MULTISAMPLE); gl.glDisable(GL2.GL_POINT_SMOOTH); gl.glDisable(GL.GL_LINE_SMOOTH); gl.glDisable(GL2.GL_POLYGON_SMOOTH); } } ////////////////////////////////////////////////////////////// // 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(PShape shape) //public void shape(PShape shape, float x, float y) //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() { Font font = textFont.getFont(); if ((textMode != SHAPE) || (font == null)) { return super.textAscent(); } FontMetrics metrics = parent.getFontMetrics(font); return metrics.getAscent(); } public float textDescent() { Font font = textFont.getFont(); if ((textMode != SHAPE) || (font == null)) { return super.textDescent(); } FontMetrics metrics = parent.getFontMetrics(font); return metrics.getDescent(); } public void textFont(PFont which) { super.textFont(which); if (textMode == SHAPE) { if (textFont.findFont() == null) { showWarning("Cannot use " + which.name + " as with textMode(SHAPE) " + "because its native equivalent cannot be found."); } } } //public void textFont(PFont which, float size) //public void textLeading(float leading) // public void textMode(int mode) { // if (mode == SHAPE) { // textMode = SHAPE; // // } else { // // if not SHAPE mode, then pass off to the PGraphics.textMode() // // which is built for error handling (but objects to SHAPE). // super.textMode(mode); // } // } protected boolean textModeCheck(int mode) { return (textMode == MODEL) || (textMode == SCREEN) || (textMode == SHAPE); } /** * Same as parent, but override for native version of the font. * <p/> * Also gets called by textFont, so the metrics * will get recorded properly. */ // public void textSize(float size) { // can't cancel on textMode(SHAPE) because textMode() must happen // after textFont() and textFont() calls textSize() //if ((textMode != SHAPE) || (textFontNative == null)) { // call this anyway to set the base variables for cases // where textMode(SHAPE) will not be used // super.textSize(size); /* // derive the font just in case the user is gonna call // textMode(SHAPE) afterwards if (textFontNative != null) { textFontNative = textFontNative.deriveFont(size); Graphics2D graphics = (Graphics2D) parent.getGraphics(); graphics.setFont(textFontNative); // get the metrics info textFontNativeMetrics = graphics.getFontMetrics(textFontNative); } */ // } //public float textWidth(char c) //public float textWidth(String str) protected float textWidthImpl(char buffer[], int start, int stop) { Font font = textFont.getFont(); if ((textMode != SHAPE) || (font == null)) { return super.textWidthImpl(buffer, start, stop); } /* // maybe should use one of the newer/fancier functions for this? int length = stop - start; return textFontNativeMetrics.charsWidth(buffer, start, length); */ Graphics2D graphics = (Graphics2D) parent.getGraphics(); // otherwise smaller sizes will be totally crapped up // seems to need to be before the getFRC, but after the canvas.getGraphics // (placing this inside textSize(), even though it was called, wasn't working) graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); FontRenderContext frc = graphics.getFontRenderContext(); GlyphVector gv; /* if (start == 0 && stop == buffer.length) { gv = textFontNative.createGlyphVector(frc, buffer); } else { char[] fellas = PApplet.subset(buffer, start, length); gv = textFontNative.createGlyphVector(frc, fellas); } */ gv = font.createGlyphVector(frc, buffer); float sum = 0; for (int i = start; i < stop; i++) { GlyphMetrics gm = gv.getGlyphMetrics(i); sum += gm.getAdvance(); } return sum; } ////////////////////////////////////////////////////////////// // TEXT // None of the variations of text() are overridden from PGraphics. ////////////////////////////////////////////////////////////// // TEXT IMPL //protected void textLineAlignImpl(char buffer[], int start, int stop, // float x, float y) //protected void textLineImpl(char buffer[], int start, int stop, // float x, float y) /** * Override to handle rendering characters with textMode(SHAPE). */ protected void textCharImpl(char ch, float x, float y) { if (textMode == SHAPE) { if (textFont.getFont() == null) { PGraphics .showWarning("textMode(SHAPE) is disabled because the font " + "\"" + textFont.name + "\" is not available."); } else { textCharShapeImpl(ch, x, y); } } else { super.textCharImpl(ch, x, y); } } /** * This uses the tesselation functions from GLU to handle triangulation * to convert the character into a series of shapes. * <p/> * <EM>No attempt has been made to optimize this code</EM> * <p/> * TODO: Should instead override textPlacedImpl() because createGlyphVector * takes a char array. Or better yet, cache the font on a per-char basis, * so that it's not being re-tessellated each time, could make it into * a display list which would be nice and speedy. * <p/> * Also a problem where some fonts seem to be a bit slight, as if the * control points aren't being mapped quite correctly. Probably doing * something dumb that the control points don't map to P5's control * points. Perhaps it's returning b-spline data from the TrueType font? * Though it seems like that would make a lot of garbage rather than * just a little flattening. * <p/> * There also seems to be a bug that is causing a line (but not a filled * triangle) back to the origin on some letters (i.e. a capital L when * tested with Akzidenz Grotesk Light). But this won't be visible * with the stroke shut off, so tabling that bug for now. */ protected void textCharShapeImpl(char ch, float x, float y) { // save the current stroke because it needs to be disabled // while the text is being drawn boolean strokeSaved = stroke; stroke = false; // six element array received from the Java2D path iterator float textPoints[] = new float[6]; // array passed to createGylphVector char textArray[] = new char[] { ch }; Graphics2D graphics = (Graphics2D) parent.getGraphics(); FontRenderContext frc = graphics.getFontRenderContext(); Font font = textFont.getFont(); GlyphVector gv = font.createGlyphVector(frc, textArray); Shape shp = gv.getOutline(); //PathIterator iter = shp.getPathIterator(null, 0.05); PathIterator iter = shp.getPathIterator(null); glu.gluTessBeginPolygon(tobj, null); // second param to gluTessVertex is for a user defined object that contains // additional info about this point, but that's not needed for anything float lastX = 0; float lastY = 0; // unfortunately the tesselator won't work properly unless a // new array of doubles is allocated for each point. that bites ass, // but also just reaffirms that in order to make things fast, // display lists will be the way to go. double vertex[]; final boolean DEBUG_OPCODES = false; //true; while (!iter.isDone()) { int type = iter.currentSegment(textPoints); switch (type) { case PathIterator.SEG_MOVETO: // 1 point (2 vars) in textPoints case PathIterator.SEG_LINETO: // 1 point if (type == PathIterator.SEG_MOVETO) { if (DEBUG_OPCODES) { System.out.println("moveto\t" + textPoints[0] + "\t" + textPoints[1]); } glu.gluTessBeginContour(tobj); } else { if (DEBUG_OPCODES) { System.out.println("lineto\t" + textPoints[0] + "\t" + textPoints[1]); } } vertex = new double[] { x + textPoints[0], y + textPoints[1], 0 }; glu.gluTessVertex(tobj, vertex, 0, vertex); lastX = textPoints[0]; lastY = textPoints[1]; break; case PathIterator.SEG_QUADTO: // 2 points if (DEBUG_OPCODES) { System.out.println("quadto\t" + textPoints[0] + "\t" + textPoints[1] + "\t" + textPoints[2] + "\t" + textPoints[3]); } for (int i = 1; i < bezierDetail; i++) { float t = (float) i / (float) bezierDetail; vertex = new double[] { x + bezierPoint( lastX, lastX + (float) ((textPoints[0] - lastX) * 2 / 3.0), textPoints[2] + (float) ((textPoints[0] - textPoints[2]) * 2 / 3.0), textPoints[2], t), y + bezierPoint( lastY, lastY + (float) ((textPoints[1] - lastY) * 2 / 3.0), textPoints[3] + (float) ((textPoints[1] - textPoints[3]) * 2 / 3.0), textPoints[3], t), 0.0f }; glu.gluTessVertex(tobj, vertex, 0, vertex); } lastX = textPoints[2]; lastY = textPoints[3]; break; case PathIterator.SEG_CUBICTO: // 3 points if (DEBUG_OPCODES) { System.out.println("cubicto\t" + textPoints[0] + "\t" + textPoints[1] + "\t" + textPoints[2] + "\t" + textPoints[3] + "\t" + textPoints[4] + "\t" + textPoints[5]); } for (int i = 1; i < bezierDetail; i++) { float t = (float) i / (float) bezierDetail; vertex = new double[] { x + bezierPoint(lastX, textPoints[0], textPoints[2], textPoints[4], t), y + bezierPoint(lastY, textPoints[1], textPoints[3], textPoints[5], t), 0 }; glu.gluTessVertex(tobj, vertex, 0, vertex); } lastX = textPoints[4]; lastY = textPoints[5]; break; case PathIterator.SEG_CLOSE: if (DEBUG_OPCODES) { System.out.println("close"); System.out.println(); } glu.gluTessEndContour(tobj); break; } iter.next(); } glu.gluTessEndPolygon(tobj); // re-enable stroke if it was in use before stroke = strokeSaved; } /** * There must be a better way to do this, but I'm having a brain fart * with all the inner class crap. Fix it later once this stuff is debugged. * <p/> * The method "void vertex(float $1, float $2, float $3);" contained in * the enclosing type "processing.core.PGraphics3" is a perfect match for * this method call. However, it is not visible in this nested class because * a method with the same name in an intervening class is hiding it. */ /* public void vertexRedirect(float x, float y, float z) { vertex(x, y, z); } */ public class TessCallback extends GLUtessellatorCallbackAdapter { public void begin(int type) { switch (type) { case GL.GL_TRIANGLE_FAN: beginShape(TRIANGLE_FAN); break; case GL.GL_TRIANGLE_STRIP: beginShape(TRIANGLE_STRIP); break; case GL.GL_TRIANGLES: beginShape(TRIANGLES); break; } } public void end() { //gl.glEnd(); endShape(); } public void edge(boolean e) { PGraphicsOpenGL.this.edge(e); } public void vertex(Object data) { if (data instanceof double[]) { double[] d = (double[]) data; if (d.length != 3) { throw new RuntimeException("TessCallback vertex() data " + "isn't length 3"); } //System.out.println("tess callback vertex " + // d[0] + " " + d[1] + " " + d[2]); //vertexRedirect((float) d[0], (float) d[1], (float) d[2]); PGraphicsOpenGL.this.vertex((float) d[0], (float) d[1], (float) d[2]); /* if (d.length == 6) { double[] d2 = {d[0], d[1], d[2]}; gl.glVertex3dv(d2); d2 = new double[]{d[3], d[4], d[5]}; gl.glColor3dv(d2); } else if (d.length == 3) { gl.glVertex3dv(d); } */ } else { throw new RuntimeException( "TessCallback vertex() data not understood"); } } public void error(int errnum) { String estring = glu.gluErrorString(errnum); PGraphics.showWarning("Tessellation Error: " + estring); } /** * Implementation of the GLU_TESS_COMBINE callback. * @param coords is the 3-vector of the new vertex * @param data is the vertex data to be combined, up to four elements. * This is useful when mixing colors together or any other * user data that was passed in to gluTessVertex. * @param weight is an array of weights, one for each element of "data" * that should be linearly combined for new values. * @param outData is the set of new values of "data" after being * put back together based on the weights. it's passed back as a * single element Object[] array because that's the closest * that Java gets to a pointer. */ public void combine(double[] coords, Object[] data, float[] weight, Object[] outData) { //System.out.println("coords.length = " + coords.length); //System.out.println("data.length = " + data.length); //System.out.println("weight.length = " + weight.length); //for (int i = 0; i < data.length; i++) { //System.out.println(i + " " + data[i].getClass().getName() + " " + weight[i]); //} double[] vertex = new double[coords.length]; vertex[0] = coords[0]; vertex[1] = coords[1]; vertex[2] = coords[2]; //System.out.println("combine " + // vertex[0] + " " + vertex[1] + " " + vertex[2]); // this is just 3, so nothing interesting to bother combining //System.out.println("data length " + ((double[]) data[0]).length); // not gonna bother doing any combining, // since no user data is being passed in. /* for (int i = 3; i < 6; i++) { vertex[i] = weight[0] * ((double[]) data[0])[i] + weight[1] * ((double[]) data[1])[i] + weight[2] * ((double[]) data[2])[i] + weight[3] * ((double[]) data[3])[i]; } */ outData[0] = vertex; } } ////////////////////////////////////////////////////////////// // MATRIX MATH //public void pushMatrix() //public void popMatrix() //public void translate(float tx, float ty) //public void translate(float tx, float ty, float tz) //public void rotate(float angle) //public void rotateX(float angle) //public void rotateY(float angle) //public void rotateZ(float angle) //public void rotate(float angle, float vx, float vy, float vz) //public void scale(float s) //public void scale(float sx, float sy) //public void scale(float x, float y, float z) //public void resetMatrix() //public void applyMatrix(PMatrix2D source) //public void applyMatrix(float n00, float n01, float n02, // float n10, float n11, float n12) //public void applyMatrix(PMatrix3D source) //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) //public getMatrix(PMatrix2D target) //public getMatrix(PMatrix3D target) //public void setMatrix(PMatrix2D source) //public void setMatrix(PMatrix3D source) //public void printMatrix() //public void beginCamera() //public void endCamera() //public void camera() //public void camera(float eyeX, float eyeY, float eyeZ, // float centerX, float centerY, float centerZ, // float upX, float upY, float upZ) //public void printCamera() //public void ortho() //public void ortho(float left, float right, // float bottom, float top, // float near, float far) //public void perspective() //public void perspective(float fov, float aspect, float near, float far) //public void frustum(float left, float right, // float bottom, float top, // float near, float far) //public void printProjection() //public float screenX(float x, float y) //public float screenY(float x, float y) //public float screenX(float x, float y, float z) //public float screenY(float x, float y, float z) //public float screenZ(float x, float y, float z) //public float modelX(float x, float y, float z) //public float modelY(float x, float y, float z) //public float modelZ(float x, float y, float z) ////////////////////////////////////////////////////////////// // 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(); gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT_AND_DIFFUSE, colorBuffer, 0); } ////////////////////////////////////////////////////////////// // 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(); gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT, colorBuffer, 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(); gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, colorBuffer, 0); } public void shininess(float shine) { super.shininess(shine); gl.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(); gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_EMISSION, colorBuffer, 0); } ////////////////////////////////////////////////////////////// // LIGHTING // We're not actually turning on GL2 lights right now // because our home-grown ones work better for now. // public void lights() { // super.lights(); // gl.glEnable(GL.GL_LIGHTING); // } // public void noLights() { // super.noLights(); // gl.glDisable(GL.GL_LIGHTING); // } public void ambientLight(float r, float g, float b) { super.ambientLight(r, g, b); glLightEnable(lightCount - 1); glLightAmbient(lightCount - 1); glLightPosition(lightCount - 1); glLightFalloff(lightCount - 1); } public void ambientLight(float r, float g, float b, float x, float y, float z) { super.ambientLight(r, g, b, x, y, z); glLightEnable(lightCount - 1); glLightAmbient(lightCount - 1); glLightPosition(lightCount - 1); glLightFalloff(lightCount - 1); } public void directionalLight(float r, float g, float b, float nx, float ny, float nz) { super.directionalLight(r, g, b, nx, ny, nz); glLightEnable(lightCount - 1); glLightNoAmbient(lightCount - 1); glLightDirection(lightCount - 1); glLightDiffuse(lightCount - 1); glLightSpecular(lightCount - 1); glLightFalloff(lightCount - 1); } public void pointLight(float r, float g, float b, float x, float y, float z) { super.pointLight(r, g, b, x, y, z); glLightEnable(lightCount - 1); glLightNoAmbient(lightCount - 1); glLightPosition(lightCount - 1); glLightDiffuse(lightCount - 1); glLightSpecular(lightCount - 1); glLightFalloff(lightCount - 1); } 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) { super.spotLight(r, g, b, x, y, z, nx, ny, nz, angle, concentration); glLightNoAmbient(lightCount - 1); glLightPosition(lightCount - 1); glLightDirection(lightCount - 1); glLightDiffuse(lightCount - 1); glLightSpecular(lightCount - 1); glLightFalloff(lightCount - 1); glLightSpotAngle(lightCount - 1); glLightSpotConcentration(lightCount - 1); } public void lightFalloff(float constant, float linear, float quadratic) { super.lightFalloff(constant, linear, quadratic); glLightFalloff(lightCount); } public void lightSpecular(float x, float y, float z) { super.lightSpecular(x, y, z); glLightSpecular(lightCount); } protected void lightPosition(int num, float x, float y, float z) { super.lightPosition(num, x, y, z); glLightPosition(num); } protected void lightDirection(int num, float x, float y, float z) { super.lightDirection(num, x, y, z); glLightDirection(num); } private void glLightAmbient(int num) { // lightBuffer.put(lightDiffuse[num]); // lightBuffer.rewind(); // gl.glLightfv(GL.GL_LIGHT0 + num, // GL.GL_AMBIENT, lightBuffer); gl.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_AMBIENT, lightDiffuse[num], 0); } private void glLightNoAmbient(int num) { if (zeroBuffer == null) { // hopefully buffers are filled with zeroes.. zeroBuffer = Buffers.newDirectFloatBuffer(3); } gl.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_AMBIENT, zeroBuffer); } private void glLightDiffuse(int num) { // lightBuffer.put(lightDiffuse[num]); // lightBuffer.rewind(); // gl.glLightfv(GL.GL_LIGHT0 + num, // GL.GL_DIFFUSE, lightBuffer); gl.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_DIFFUSE, lightDiffuse[num], 0); } private void glLightDirection(int num) { // lightBuffer.put(lightNormal[num]); // lightBuffer.rewind(); 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" gl.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_POSITION, lightNormal[num] .array(), 0); } else { // spotlight // this one only needs the 3 arg version gl.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_SPOT_DIRECTION, lightNormal[num].array(), 0); } } private void glLightEnable(int num) { gl.glEnable(GL2.GL_LIGHT0 + num); } private void glLightFalloff(int num) { gl.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_CONSTANT_ATTENUATION, lightFalloffConstant[num]); gl.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_LINEAR_ATTENUATION, lightFalloffLinear[num]); gl.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_QUADRATIC_ATTENUATION, lightFalloffQuadratic[num]); } private void glLightPosition(int num) { gl.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_POSITION, lightPosition[num] .array(), 0); } private void glLightSpecular(int num) { gl.glLightfv(GL2.GL_LIGHT0 + num, GL2.GL_SPECULAR, lightSpecular[num], 0); } private void glLightSpotAngle(int num) { gl.glLightf(GL2.GL_LIGHT0 + num, GL2.GL_SPOT_CUTOFF, lightSpotAngle[num]); } private void glLightSpotConcentration(int num) { gl.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 OpenGL2 complement to the colorCalc() methods. /** * Load the calculated color into a pre-allocated array so that * it can be quickly passed over to OpenGL. */ private final void calcColorBuffer() { if (colorBuffer == null) { // colorBuffer = BufferUtil.newFloatBuffer(4); colorBuffer = new float[4]; } colorBuffer[0] = calcR; colorBuffer[1] = calcG; colorBuffer[2] = calcB; colorBuffer[3] = calcA; // colorBuffer.put(0, calcR); // colorBuffer.put(1, calcG); // colorBuffer.put(2, calcB); // colorBuffer.put(3, calcA); // colorBuffer.rewind(); } ////////////////////////////////////////////////////////////// // 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) { String errString = glu.gluErrorString(err); String msg = "OpenGL2 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 = Buffers.newDirectIntBuffer(pixels.length); } /* for (int y = 0; y < height; y++) { // or SKIP_PIXELS with y*width //gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, (height-1) - y); gl.glReadPixels(0, y, width, y + 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixels); } gl.glPixelStorei(GL.GL_PACK_SKIP_ROWS, 0); */ gl.glReadPixels(0, 0, width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixelBuffer); pixelBuffer.get(pixels); pixelBuffer.rewind(); //for (int i = 0; i < 5; i++) { //System.out.println(PApplet.hex(pixels[i])); //} /* int temp[] = new int[width]; // 3 rows, skips the middle for (int y = 0; y < height/2; y++) { int yy = (height - 1) - y; System.arraycopy(pixels, y*width, temp, 0, width); System.arraycopy(pixels, yy*width, pixels, y*width, width); System.arraycopy(temp, 0, pixels, yy*width, width); } */ /* // now need to swap the RGBA components to ARGB (big endian) for (int i = 0; i < pixels.length; i++) { //pixels[i] = ((pixels[i] & 0xff) << 24) | pixels[i] = ((pixels[i] << 24) & 0xff) | // safer? ((pixels[i] >> 8) & 0xffffff); } */ // 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 OpenGL2 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 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 OpenGL2 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 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 OpenGL2 format. * This function leaves alone (ignores) the alpha component. * Also flips the image vertically, since images are upside-down in GL. */ static 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 OpenGL2 format. * Also flips the image vertically, since images are upside-down in GL. */ static 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; } // re-pack ARGB data into RGBA for opengl (big endian) //for (int i = 0; i < pixels.length; i++) { //pixels[i] = ((pixels[i] >> 24) & 0xff) | //((pixels[i] << 8) & 0xffffff00); //} setRasterPos(0, 0); // lower-left corner pixelBuffer.put(pixels); pixelBuffer.rewind(); gl.glDrawPixels(width, height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, pixelBuffer); } ////////////////////////////////////////////////////////////// // RESIZE public void resize(int wide, int high) { PGraphics.showMethodWarning("resize"); } ////////////////////////////////////////////////////////////// // GET/SET IntBuffer getsetBuffer = Buffers.newDirectIntBuffer(1); public int get(int x, int y) { gl.glReadPixels(x, y, 1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, getsetBuffer); // gl.glReadPixels(x, y, 1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, getset, 0); 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 = new PImage(w, h); //new int[w*h], w, h, ARGB); IntBuffer newbieBuffer = Buffers.newDirectIntBuffer(w * h); gl.glReadPixels(x, y, w, h, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, newbieBuffer); newbieBuffer.get(newbie.pixels); nativeToJavaARGB(newbie); 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); } getsetBuffer.put(0, getset); getsetBuffer.rewind(); //gl.glRasterPos2f(x + EPSILON, y + EPSILON); setRasterPos(x, (height - y) - 1); gl.glDrawPixels(1, 1, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, getsetBuffer); } /** * Set an image directly to the screen. * <P> * TODO not optimized properly, creates multiple temporary buffers * the size of the image. Needs to instead use image cache, but that * requires two types of image cache. One for power of 2 textures * and another for glReadPixels/glDrawPixels data that's flipped * vertically. Both have their components all swapped to native. */ public void set(int x, int y, PImage source) { int[] backup = new int[source.pixels.length]; System.arraycopy(source.pixels, 0, backup, 0, source.pixels.length); javaToNativeARGB(source); // TODO is this possible without intbuffer? IntBuffer setBuffer = Buffers.newDirectIntBuffer(source.pixels.length); setBuffer.put(source.pixels); setBuffer.rewind(); setRasterPos(x, (height - y) - source.height); //+source.height); gl.glDrawPixels(source.width, source.height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, setBuffer); source.pixels = backup; } // TODO remove the implementation above and use setImpl instead, // since it'll be more efficient // http://dev.processing.org/bugs/show_bug.cgi?id=943 //protected void setImpl(int dx, int dy, int sx, int sy, int sw, int sh, // PImage src) /** * Definitive method for setting raster pos, including offscreen locations. * The raster position is tricky because it's affected by the modelview and * projection matrices. Further, offscreen coords won't properly set the * raster position. This code gets around both issues. * http://www.mesa3d.org/brianp/sig97/gotchas.htm * @param y the Y-coordinate, which is flipped upside down in OpenGL */ protected void setRasterPos(float x, float y) { float z = 0; float w = 1; float fx, fy; // Push current matrix mode and viewport attributes gl.glPushAttrib(GL2.GL_TRANSFORM_BIT | GL2.GL_VIEWPORT_BIT); // Setup projection parameters gl.glMatrixMode(GL2.GL_PROJECTION); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glMatrixMode(GL2.GL_MODELVIEW); gl.glPushMatrix(); gl.glLoadIdentity(); gl.glDepthRange(z, z); gl.glViewport((int) x - 1, (int) y - 1, 2, 2); // set the raster (window) position fx = x - (int) x; fy = y - (int) y; gl.glRasterPos4f(fx, fy, 0, w); // restore matrices, viewport and matrix mode gl.glPopMatrix(); gl.glMatrixMode(GL2.GL_PROJECTION); gl.glPopMatrix(); gl.glPopAttrib(); } ////////////////////////////////////////////////////////////// // 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 GL2 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) // this function was removed // 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)); // } // the following two were removed for 1.0.2 because loadPixels and // updatePixels are now called in the superclass. // http://dev.processing.org/bugs/show_bug.cgi?id=1137 /** * Extremely slow and not optimized, should use GL2 methods instead. * Currently calls a beginPixels() on the whole canvas, then does the copy, * then it calls endPixels(). Please help fix: * <A HREF="http://dev.processing.org/bugs/show_bug.cgi?id=941">Bug 941</A>, * <A HREF="http://dev.processing.org/bugs/show_bug.cgi?id=942">Bug 942</A>. */ // 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(); // } ////////////////////////////////////////////////////////////// // SAVE //public void save(String filename) // PImage calls loadPixels() ////////////////////////////////////////////////////////////// // INTERNAL MATH protected final float clamp(float a) { return (a < 1) ? a : 1; } }