diff --git a/android/core/src/processing/core/PApplet.java b/android/core/src/processing/core/PApplet.java index d96d1abcd..fae0c8db0 100644 --- a/android/core/src/processing/core/PApplet.java +++ b/android/core/src/processing/core/PApplet.java @@ -1502,7 +1502,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public PImage createImage(int wide, int high, int format) { PImage image = new PImage(wide, high, format); image.parent = this; // make save() work - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { // TODO: Check why textures doesn't work in formats other than ARGB... image.format = ARGB; image.loadTexture(); @@ -1514,7 +1514,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public PImage createImage(int wide, int high, int format, int filter) { PImage image = new PImage(wide, high, format); image.parent = this; // make save() work - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { // TODO: Check why textures doesn't work in formats other than ARGB... image.format = ARGB; image.loadTexture(filter); @@ -1526,7 +1526,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public PImage createImage(int wide, int high, PTexture.Parameters params) { PImage image = new PImage(wide, high, params.format); image.parent = this; // make save() work - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { // TODO: Check why textures doesn't work in formats other than ARGB... image.format = ARGB; image.loadTexture(params); @@ -3314,7 +3314,7 @@ public class PApplet extends Activity implements PConstants, Runnable { // println("loadImage(" + filename + ") was " + nfc(much)); PImage image = new PImage(bitmap); image.parent = this; - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { // TODO: Check why textures doesn't work in formats other than ARGB... image.format = ARGB; image.loadTexture(params); @@ -3493,7 +3493,7 @@ public class PApplet extends Activity implements PConstants, Runnable { e.printStackTrace(); } } else if (filename.toLowerCase().endsWith(".obj")) { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { return new PShape3D(this, filename, mode); } else { throw new RuntimeException("OBJ files can be loaded only when using the A3D renderer."); @@ -3515,7 +3515,7 @@ public class PApplet extends Activity implements PConstants, Runnable { * Creates an empty 3D shape, with space for nvert vertices. */ public PShape3D createShape(int nvert, int kind, int mode) { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { PShape3D.Parameters params = PShape3D.newParameters(kind, mode); PShape3D model = new PShape3D(this, nvert, params); return model; @@ -3537,7 +3537,7 @@ public class PApplet extends Activity implements PConstants, Runnable { * Tesselates a PShape into a PShape3D with the desired drawing mode (STATID or DYNAMIC).. */ public PShape3D createShape(PShape shape, int mode) { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { PGraphicsAndroid3D a3d = (PGraphicsAndroid3D)g; a3d.beginShapeRecorderImpl(); shape(shape, 0, 0, 1, 1); @@ -6921,7 +6921,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public PShape beginRecord() { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { return ((PGraphicsAndroid3D) g).beginRecord(); } else { throw new RuntimeException("The shapes recorder can only be used with the A3D renderer."); @@ -6930,7 +6930,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public void beginShapesRecorder() { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { ((PGraphicsAndroid3D) g).beginShapesRecorder(); } else { throw new RuntimeException("The shapes recorder can only be used with the A3D renderer."); @@ -6939,7 +6939,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public void beginShapeRecorder() { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { ((PGraphicsAndroid3D) g).beginShapeRecorder(); } else { throw new RuntimeException("The shape recorder can only be used with the A3D renderer."); @@ -6948,7 +6948,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public void beginShapeRecorder(int kind) { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { ((PGraphicsAndroid3D) g).beginShapeRecorder(kind); } else { throw new RuntimeException("The shape recorder can only be used with the A3D renderer."); @@ -7022,7 +7022,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public void endRecord() { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { ((PGraphicsAndroid3D) g).endRecord(); } else { throw new RuntimeException("The shapes recorder can only be used with the A3D renderer."); @@ -7031,7 +7031,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public PShape3D endShapesRecorder() { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { return ((PGraphicsAndroid3D) g).endShapesRecorder(); } else { throw new RuntimeException("The shapes recorder can only be used with the A3D renderer."); @@ -7040,7 +7040,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public PShape3D endShapeRecorder() { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { return ((PGraphicsAndroid3D) g).endShapeRecorder(); } else { throw new RuntimeException("The shape recorder can only be used with the A3D renderer."); @@ -7049,7 +7049,7 @@ public class PApplet extends Activity implements PConstants, Runnable { public PShape3D endShapeRecorder(int mode) { - if (g instanceof PGraphicsAndroid3D) { + if (g.is3D()) { return ((PGraphicsAndroid3D) g).endShapeRecorder(mode); } else { throw new RuntimeException("The shape recorder can only be used with the A3D renderer."); diff --git a/android/core/src/processing/core/PGraphics.java b/android/core/src/processing/core/PGraphics.java index 6b70d23dd..aeab2a7cb 100644 --- a/android/core/src/processing/core/PGraphics.java +++ b/android/core/src/processing/core/PGraphics.java @@ -5094,7 +5094,7 @@ public class PGraphics extends PImage implements PConstants { // TODO: Discuss proper integration into PGraphics API. public PImage getOffscreenImage() { - if (!(this instanceof PGraphicsAndroid3D)) { + if (!is3D()) { showMissingWarning("getOffscreenImage"); } return null; @@ -5102,14 +5102,14 @@ public class PGraphics extends PImage implements PConstants { public void blend(int mode) { - if (!(this instanceof PGraphicsAndroid3D)) { + if (!is3D()) { showMissingWarning("blend"); } } public void noBlend() { - if (!(this instanceof PGraphicsAndroid3D)) { + if (!is3D()) { showMissingWarning("noBlend"); } } diff --git a/android/core/src/processing/core/PGraphicsAndroid3D.java b/android/core/src/processing/core/PGraphicsAndroid3D.java index 9c07aadca..5b490929e 100644 --- a/android/core/src/processing/core/PGraphicsAndroid3D.java +++ b/android/core/src/processing/core/PGraphicsAndroid3D.java @@ -51,7 +51,7 @@ import processing.core.PShape3D.VertexGroup; // setRasterPos() is also commented out /* - * Android 3D renderer implemented with pure OpenGL ES 1.0/1.1 + * Android 3D renderer implemented with pure OpenGL ES 1.1 * By Andres Colubri * * TODO: Comment A3D, PShape3D, PTexture, PFramebuffer, @@ -92,9 +92,6 @@ public class PGraphicsAndroid3D extends PGraphics { /** Aspect ratio of camera's view. */ public float cameraAspect; - protected float currentEyeX; - protected float currentCenterY; - /** Modelview and projection matrices **/ // Array version for use with OpenGL @@ -3434,11 +3431,7 @@ public class PGraphicsAndroid3D extends PGraphics { float centerY, float centerZ, float upX, float upY, float upZ) { eyeY = height - eyeY; centerY = height - centerY; - - // Needed for properly setting ortographic projection. - currentEyeX = eyeX; - currentCenterY = centerY; - + // Calculating Z vector float z0 = eyeX - centerX; float z1 = eyeY - centerY; @@ -3564,20 +3557,21 @@ public class PGraphicsAndroid3D extends PGraphics { } /** - * Properly setting the ortographic projection depends on the values - * used to set the camera (eye and center). + * 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) { - // TODO: check if this equation is correct in the case the camera is not aligned - // to the XYZ axis (I guess the eye-camera vector needs to be used to determine - // the amounds to substract from left,right,bottom and top). - left -= currentEyeX; - right -= currentEyeX; + left -= width/2; + right -= width/2; - bottom -= currentCenterY; - top -= currentCenterY; + bottom -= height/2; + top -= height/2; float x = 2.0f / (right - left); float y = 2.0f / (top - bottom); @@ -4927,8 +4921,8 @@ public class PGraphicsAndroid3D extends PGraphics { // Setting texture crop. gl11.glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES, crop, 0); - // There is no need to setup orthographic projection or any related matrix set/restore - // operations here because glDrawTexiOES operates on window coordinates directly: + // There is no need to setup orthographic projection or call any related matrix set/restore + // functions here because glDrawTexiOES operates on window coordinates directly: // "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." @@ -5418,6 +5412,13 @@ public class PGraphicsAndroid3D extends PGraphics { + printConfig(egl, display, bestConfig); System.out.println(configStr); } + PApplet.println("OPENGL DISPLAY CONFIG:"); + PApplet.println("redBits " + redBits); + PApplet.println("greenBits " + greenBits); + PApplet.println("blueBits " + blueBits); + PApplet.println("alphaBits " + alphaBits); + PApplet.println("depthBits " + depthBits); + PApplet.println("stencilBits " + stencilBits); return bestConfig; } diff --git a/android/core/src/processing/core/PImage.java b/android/core/src/processing/core/PImage.java index 367a8e5bf..8cc2689ff 100644 --- a/android/core/src/processing/core/PImage.java +++ b/android/core/src/processing/core/PImage.java @@ -59,6 +59,16 @@ public class PImage implements PConstants, Cloneable { protected Bitmap bitmap; protected PTexture texture; + + // This boolean variable is used to indicate that there + // was a change in the contents of the pixels array and + // hasn't been transfered to the texture yet. + protected boolean texUpdated = false; + + // This boolean variable is used to indicate that there + // was a change in the contents of the texture and + // hasn't been transfered to the pixels array yet. + protected boolean pixUpdated = true; // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -192,44 +202,79 @@ public class PImage implements PConstants, Cloneable { public void loadTexture() { - texture = new PTexture(parent, width, height, new PTexture.Parameters(format)); - pixelsToTexture(); + if (texture == null) { + texture = new PTexture(parent, width, height, new PTexture.Parameters(format)); + texUpdated = false; + } + if (pixels != null) { + // loadTexture has lower "permissions" than loadPixels, because + // loadPixels calls loadTexture which in turns creates the texture + // if it is null, but the contrary doesn't happen (loadTexture cannot + // result in the creation of the pixels array if it is null). + pixelsToTexture(); + } } public void loadTexture(int filter) { - texture = new PTexture(parent, width, height, new PTexture.Parameters(format, filter)); - pixelsToTexture(); + if (texture == null) { + texture = new PTexture(parent, width, height, new PTexture.Parameters(format, filter)); + texUpdated = false; + } + if (pixels != null) { + pixelsToTexture(); + } } public void loadTexture(PTexture.Parameters params) { - texture = new PTexture(parent, width, height, params); - pixelsToTexture(); + if (texture == null) { + texture = new PTexture(parent, width, height, params); + texUpdated = false; + } + if (pixels != null) { + pixelsToTexture(); + } } - - public void pixelsToTexture() { - loadPixels(); - texture.set(this); + + public void setTexture(PTexture tex) { + if (tex.width == width && tex.height == height) { + texture.set(tex); + pixUpdated = false; + } else { + System.err.println("PImage: cannot set texture with different resolution from PImage object"); + } } - - public void textureToPixels() { - loadPixels(); - texture.get(pixels); - } - - - public void setTexture(PTexture texture) { - this.texture = texture; - } - - + public PTexture getTexture() { return texture; } + + public void updateTexture() { + if (pixels != null) { + textureToPixels(); + } + } + + + protected void pixelsToTexture() { + if (!texUpdated) { + texture.set(pixels); + texUpdated = true; + } + } + + + protected void textureToPixels() { + if (!pixUpdated) { + texture.get(pixels); + pixUpdated = true; + } + } + ////////////////////////////////////////////////////////////// @@ -306,9 +351,8 @@ public class PImage implements PConstants, Cloneable { if (bitmap != null) { bitmap.getPixels(pixels, 0, width, 0, 0, width, height); } - - if (texture != null) { - // texture.get(pixels); HERE? + if (parent.g.is3D()) { + loadTexture(); } } @@ -369,12 +413,15 @@ public class PImage implements PConstants, Cloneable { } if (texture != null) { - // texture.set(this); HERE? + texture.set(pixels, mx1, my1, mx2 - mx1, my2 - my1); + // Assuming in good faith that the user only messed up + // with the pixels in the specified region. We don't have + // any way to know if he or she is lying. + texUpdated = true; } } - ////////////////////////////////////////////////////////////// // COPYING IMAGE DATA @@ -526,7 +573,7 @@ public class PImage implements PConstants, Cloneable { index2 += w; } } - if (parent.g instanceof PGraphicsAndroid3D) { + if (parent.g.is3D()) { // TODO: Check why textures doesn't work in formats other than ARGB... newbie.format = ARGB; newbie.loadTexture(); @@ -541,7 +588,7 @@ public class PImage implements PConstants, Cloneable { public PImage get() { try { PImage img = (PImage) clone(); - if (parent.g instanceof PGraphicsAndroid3D) { + if (parent.g.is3D()) { // TODO: Check why textures doesn't work in formats other than ARGB... img.format = ARGB; img.loadTexture(); @@ -2744,4 +2791,3 @@ public class PImage implements PConstants, Cloneable { //return success; } } - diff --git a/android/core/src/processing/core/PTexture.java b/android/core/src/processing/core/PTexture.java index e1bcf5996..beedc1944 100644 --- a/android/core/src/processing/core/PTexture.java +++ b/android/core/src/processing/core/PTexture.java @@ -35,10 +35,9 @@ import android.opengl.GLUtils; import java.nio.*; /** - * This class adds an opengl texture to a PImage object. + * This class wraps an OpenGL texture. * By Andres Colubri - * TODO: Finish integration with PImage - * TODO: Revise updating mechanism (what happens when the pixels change in the PImage, etc). + * */ @SuppressWarnings("unused") public class PTexture implements PConstants { @@ -163,7 +162,7 @@ public class PTexture implements PConstants { /** * 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 + * it first destroys the current OpenGL texture object and then creates a new one with the specified * size. * @param width int * @param height int @@ -175,7 +174,7 @@ public class PTexture implements PConstants { /** * 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 + * 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 @@ -190,17 +189,18 @@ public class PTexture implements PConstants { public void resize(int wide, int high) { - // Creating new texture with the apropriate size. + // 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); - // Now, overwriting this with tex. + // Now, overwriting "this" with tex. copy(tex); // Zeroing the texture id of tex, so the texture is not - // deleted by opengl when the object is finalized by the GC. + // deleted by OpenGL when the object is finalized by the GC. + // "This" texture now wraps the one created in tex. tex.glTextureID = 0; } @@ -224,10 +224,9 @@ public class PTexture implements PConstants { width = img.width; height = img.height; createTexture(width, height); - } - + } img.loadPixels(); - set(img.pixels, img.format); + set(img.texture); } @@ -243,42 +242,58 @@ public class PTexture implements PConstants { width = h; createTexture(w, h); } - - img.loadPixels(); - int p0; - int dest[] = new int[w * h]; - for (int j = 0; j < h; j++) { - p0 = y * img.width + x + (img.width - w) * j; - PApplet.arrayCopy(img.pixels, p0 + w * j, dest, w * j, w); - } - - set(dest, img.format); + img.loadPixels(); + set(img.texture, x, y, w, h); + } + + + public void set(PTexture tex) { + set(tex, 0, 0, tex.width, tex.height); } // Copies source texture to this by means of FBO. - public void set(PTexture tex) { + public void set(PTexture tex, int x, int y, int w, int h) { + if (tex == null) { + throw new RuntimeException("PTexture: source texture is null"); + } + PFramebuffer fbo = new PFramebuffer(parent, glWidth, glHeight); - // This is the color (destination) buffer of the FBO. + // This texture is the color (destination) buffer of the FBO. fbo.setColorBuffer(this); fbo.disableDepthTest(); // FBO copy: a3d.pushFramebuffer(); a3d.setFramebuffer(fbo); - // Rendering tex into this. - a3d.drawTexture(tex, 0, 0, tex.glWidth, tex.glHeight, 0, 0, glWidth, glHeight); + // Rendering tex into "this", and scaling the source rectangle + // to cover the entire destination region. + a3d.drawTexture(tex, x, y, w, h, 0, 0, width, height); a3d.popFramebuffer(); } public void set(int[] pixels) { - set(pixels, ARGB); + set(pixels, 0, 0, glWidth, glHeight, ARGB); } - public void set(int[] intArray, int arrayFormat) { - if (intArray.length != width * height) { + public void set(int[] pixels, int format) { + set(pixels, 0, 0, glWidth, glHeight, 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 != width * height) { throw new RuntimeException("PTexture: wrong length of pixels array"); } @@ -286,35 +301,53 @@ public class PTexture implements PConstants { createTexture(width, height); } - int[] convArray = new int[glWidth * glHeight]; + /* + int p0; + int dest[] = new int[w * h]; + for (int j = 0; j < h; j++) { + p0 = y * img.width + x + (img.width - w) * j; + PApplet.arrayCopy(img.pixels, p0 + w * j, dest, w * j, w); + } + int p0; + int dest[] = new int[w * h]; + for (int j = 0; j < h; j++) { + p0 = y * img.width + x + (img.width - w) * j; + PApplet.arrayCopy(img.pixels, p0 + w * j, dest, w * j, w); + } + */ gl.glEnable(glTarget); gl.glBindTexture(glTarget, glTextureID); if (usingMipmaps) { if (a3d.gl11 != null && PGraphicsAndroid3D.mipmapSupported) { - convertToRGBA(intArray, convArray, arrayFormat); + int[] rgbaPixels = new int[glWidth * glHeight]; + convertToRGBA(pixels, rgbaPixels, format, 0, 0, width, height); gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_GENERATE_MIPMAP, GL11.GL_TRUE); - gl.glTexSubImage2D(glTarget, 0, 0, 0, glWidth, glHeight, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, IntBuffer.wrap(convArray)); + gl.glTexSubImage2D(glTarget, 0, x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, IntBuffer.wrap(rgbaPixels)); } else { + /* // Code by Mike Miller obtained from here: // http://insanitydesign.com/wp/2009/08/01/android-opengl-es-mipmaps/ - copyARGB(intArray, convArray); + // TODO: Check if this algorithm works only for pot textures or for any resolution. + // and adapt for copying only one texture rectangle. + int[] argbPixels = new int[glWidth * glHeight]; + copyARGB(pixels, argbPixels); int level = 0; - int w = glWidth; - int h = glHeight; + int w0 = glWidth; + int h0 = glHeight; // We create a Bitmap because then we use its built-in filtered downsampling // functionality. Bitmap bitmap = Bitmap.createBitmap(w, h, Config.ARGB_8888); - bitmap.setPixels(convArray, 0, w, 0, 0, w, h); + bitmap.setPixels(argbPixels, 0, w0, 0, 0, w0, h0); - while (w >= 1 || h >= 1) { + while (w0 >= 1 || h0 >= 1) { //First of all, generate the texture from our bitmap and set it to the according level GLUtils.texImage2D(GL10.GL_TEXTURE_2D, level, bitmap, 0); - + // We are done. - if (w == 1 || h == 1) { + if (w0 == 1 || h0 == 1) { break; } @@ -322,18 +355,25 @@ public class PTexture implements PConstants { level++; // Downsampling bitmap - h /= 2; - w /= 2; - Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, w, h, true); + h0 /= 2; + w0 /= 2; + Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, w0, h0, true); // Clean up bitmap.recycle(); bitmap = bitmap2; } + */ + // No mipmaps for now. + int[] rgbaPixels = new int[glWidth * glHeight]; + convertToRGBA(pixels, rgbaPixels, format, 0, 0, width, height); + gl.glTexSubImage2D(glTarget, 0, x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, IntBuffer.wrap(rgbaPixels)); + } } else { - convertToRGBA(intArray, convArray, arrayFormat); - gl.glTexSubImage2D(glTarget, 0, 0, 0, glWidth, glHeight, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, IntBuffer.wrap(convArray)); + int[] rgbaPixels = new int[glWidth * glHeight]; + convertToRGBA(pixels, rgbaPixels, format, 0, 0, width, height); + gl.glTexSubImage2D(glTarget, 0, x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, IntBuffer.wrap(rgbaPixels)); } gl.glDisable(glTarget); @@ -342,13 +382,15 @@ public class PTexture implements PConstants { //////////////////////////////////////////////////////////// - // Update methods + // 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]; } @@ -398,7 +440,7 @@ public class PTexture implements PConstants { //////////////////////////////////////////////////////////// - // Get opengl parameters + // Get OpenGL parameters protected int getGLWidth() { @@ -412,7 +454,7 @@ public class PTexture implements PConstants { /** - * Provides the ID of the opengll texture object. + * Provides the ID of the OpenGL texture object. * @return int */ protected int getGLTextureID() { @@ -592,7 +634,8 @@ public class PTexture implements PConstants { * @param tIntArray int[] * @param arrayFormat int */ - protected void convertToRGBA(int[] intArray, int[] tIntArray, int arrayFormat) { + // TODO: Finish handling of rectangular area. + protected void convertToRGBA(int[] intArray, int[] tIntArray, int arrayFormat, int x0, int y0, int w, int h) { int t = 0; int p = 0; if (PGraphicsAndroid3D.BIG_ENDIAN) {