package com.iwedia.gui.osd.curleffect; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.PointF; import android.graphics.RectF; import android.opengl.GLUtils; import com.iwedia.gui.config_handler.ConfigHandler; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; import javax.microedition.khronos.opengles.GL10; /** * Class for Creating Curl Polygons */ public class CurlMesh { /** Flag for enabling shadow rendering. */ private static final boolean DRAW_SHADOW = true; /** Flag for texture rendering. */ private static final boolean DRAW_TEXTURE = true; /** * Colors for shadow. Inner one is the color drawn next to surface where * shadowed area starts and outer one is color shadow ends to. */ private static final float[] SHADOW_INNER_COLOR = { 0f, 0f, 0f, 0f }; private static final float[] SHADOW_OUTER_COLOR = { 0f, 0f, 0f, 0f }; /** * Let's avoid using 'new' as much as possible. Meaning we introduce arrays * once here and reuse them on runtime. Doesn't really have very much effect * but avoids some garbage collections from happening. */ private Array<ShadowVertex> mArrDropShadowVertices; private Array<Vertex> mArrIntersections; private Array<Vertex> mArrOutputVertices; private Array<Vertex> mArrRotatedVertices; private Array<Double> mArrScanLines; private Array<ShadowVertex> mArrSelfShadowVertices; private Array<ShadowVertex> mArrTempShadowVertices; private Array<Vertex> mArrTempVertices; /** Buffers for feeding rasterizer. */ private FloatBuffer mBufColors; private FloatBuffer mBufShadowColors; private FloatBuffer mBufShadowVertices; private FloatBuffer mBufTexCoords; private FloatBuffer mBufVertices; private int mDropShadowCount; /** Boolean for 'flipping' texture sideways. */ private boolean mFlipTexture = false; /** Maximum number of split lines used for creating a curl. */ private int mMaxCurlSplits; /** * Bounding rectangle for this mesh. mRectagle[0] = top-left corner, * mRectangle[1] = bottom-left, mRectangle[2] = top-right and mRectangle[3] * bottom-right. */ private final Vertex[] mRectangle = new Vertex[4]; private int mSelfShadowCount; private boolean mTextureBack = false; /** Texture ids and other variables. */ private int[] mTextureIds = null; private final CurlPage mTexturePage = new CurlPage(); private final RectF mTextureRectBack = new RectF(); private final RectF mTextureRectFront = new RectF(); private int mVerticesCountBack; private int mVerticesCountFront; /** * Constructor for mesh object. * * @param maxCurlSplits * Maximum number curl can be divided into. The bigger the value the * smoother curl will be. With the cost of having more polygons for * drawing. */ public CurlMesh(int maxCurlSplits) { // There really is no use for 0 splits. mMaxCurlSplits = maxCurlSplits < 1 ? 1 : maxCurlSplits; mArrScanLines = new Array<Double>(maxCurlSplits + 2); mArrOutputVertices = new Array<Vertex>(7); mArrRotatedVertices = new Array<Vertex>(4); mArrIntersections = new Array<Vertex>(2); mArrTempVertices = new Array<Vertex>(7 + 4); for (int i = 0; i < 7 + 4; ++i) { mArrTempVertices.add(new Vertex()); } if (DRAW_SHADOW) { mArrSelfShadowVertices = new Array<ShadowVertex>( (mMaxCurlSplits + 2) * 2); mArrDropShadowVertices = new Array<ShadowVertex>( (mMaxCurlSplits + 2) * 2); mArrTempShadowVertices = new Array<ShadowVertex>( (mMaxCurlSplits + 2) * 2); for (int i = 0; i < (mMaxCurlSplits + 2) * 2; ++i) { mArrTempShadowVertices.add(new ShadowVertex()); } } // Rectangle consists of 4 vertices. Index 0 = top-left, index 1 = // bottom-left, index 2 = top-right and index 3 = bottom-right. for (int i = 0; i < 4; ++i) { mRectangle[i] = new Vertex(); } // Set up shadow penumbra direction to each vertex. We do fake 'self // shadow' calculations based on this information. mRectangle[0].mPenumbraX = -1; mRectangle[1].mPenumbraX = -1; mRectangle[1].mPenumbraY = -1; mRectangle[3].mPenumbraY = -1; mRectangle[0].mPenumbraY = 1; mRectangle[2].mPenumbraX = 1; mRectangle[2].mPenumbraY = 1; mRectangle[3].mPenumbraX = 1; // There are 4 vertices from bounding rect, max 2 from adding split line // to two corners and curl consists of max mMaxCurlSplits lines each // outputting 2 vertices. int maxVerticesCount = 4 + 2 + (2 * mMaxCurlSplits); ByteBuffer vbb = ByteBuffer.allocateDirect(maxVerticesCount * 3 * 4); vbb.order(ByteOrder.nativeOrder()); mBufVertices = vbb.asFloatBuffer(); mBufVertices.position(0); if (DRAW_TEXTURE) { ByteBuffer tbb = ByteBuffer .allocateDirect(maxVerticesCount * 2 * 4); tbb.order(ByteOrder.nativeOrder()); mBufTexCoords = tbb.asFloatBuffer(); mBufTexCoords.position(0); } ByteBuffer cbb = ByteBuffer.allocateDirect(maxVerticesCount * 4 * 4); cbb.order(ByteOrder.nativeOrder()); mBufColors = cbb.asFloatBuffer(); mBufColors.position(0); if (DRAW_SHADOW) { int maxShadowVerticesCount = (mMaxCurlSplits + 2) * 2 * 2; ByteBuffer scbb = ByteBuffer .allocateDirect(maxShadowVerticesCount * 4 * 4); scbb.order(ByteOrder.nativeOrder()); mBufShadowColors = scbb.asFloatBuffer(); mBufShadowColors.position(0); ByteBuffer sibb = ByteBuffer .allocateDirect(maxShadowVerticesCount * 3 * 4); sibb.order(ByteOrder.nativeOrder()); mBufShadowVertices = sibb.asFloatBuffer(); mBufShadowVertices.position(0); mDropShadowCount = 0; mSelfShadowCount = 0; } } /** Adds vertex to buffers. */ private void addVertex(Vertex vertex) { mBufVertices.put((float) vertex.mPosX); mBufVertices.put((float) vertex.mPosY); mBufVertices.put((float) vertex.mPosZ); mBufColors.put(vertex.mColorFactor * Color.red(vertex.mColor) / 255f); mBufColors.put(vertex.mColorFactor * Color.green(vertex.mColor) / 255f); mBufColors.put(vertex.mColorFactor * Color.blue(vertex.mColor) / 255f); mBufColors.put(Color.alpha(vertex.mColor) / 255f); if (DRAW_TEXTURE) { mBufTexCoords.put((float) vertex.mTexX); mBufTexCoords.put((float) vertex.mTexY); } } /** * Sets curl for this mesh. * * @param curlPos * Position for curl 'center'. Can be any point on line collinear to * curl. * @param curlDir * Curl direction, should be normalized. * @param radius * Radius of curl. */ public synchronized void curl(PointF curlPos, PointF curlDir, double radius) { // First add some 'helper' lines used for development. // Actual 'curl' implementation starts here. mBufVertices.position(0); mBufColors.position(0); if (DRAW_TEXTURE) { mBufTexCoords.position(0); } // Calculate curl angle from direction. double curlAngle = Math.acos(curlDir.x); curlAngle = curlDir.y > 0 ? -curlAngle : curlAngle; // Initiate rotated rectangle which's is translated to curlPos and // rotated so that curl direction heads to right (1,0). Vertices are // ordered in ascending order based on x -coordinate at the same time. // And using y -coordinate in very rare case in which two vertices have // same x -coordinate. mArrTempVertices.addAll(mArrRotatedVertices); mArrRotatedVertices.clear(); for (int i = 0; i < 4; ++i) { Vertex v = mArrTempVertices.remove(0); v.set(mRectangle[i]); v.translate(-curlPos.x, -curlPos.y); v.rotateZ(-curlAngle); int j = 0; for (; j < mArrRotatedVertices.size(); ++j) { Vertex v2 = mArrRotatedVertices.get(j); if (v.mPosX > v2.mPosX) { break; } if (Math.abs(v.mPosX - v2.mPosX) < 0.001 && v.mPosY > v2.mPosY) { break; } } mArrRotatedVertices.add(j, v); } // Rotated rectangle lines/vertex indices. We need to find bounding // lines for rotated rectangle. After sorting vertices according to // their x -coordinate we don't have to worry about vertices at indices // 0 and 1. But due to inaccuracy it's possible vertex 3 is not the // opposing corner from vertex 0. So we are calculating distance from // vertex 0 to vertices 2 and 3 - and altering line indices if needed. // Also vertices/lines are given in an order first one has x -coordinate // at least the latter one. This property is used in getIntersections to // see if there is an intersection. int lines[][] = { { 0, 1 }, { 0, 2 }, { 1, 3 }, { 2, 3 } }; { Vertex v0 = mArrRotatedVertices.get(0); Vertex v2 = mArrRotatedVertices.get(2); Vertex v3 = mArrRotatedVertices.get(3); double dist2 = Math.sqrt((v0.mPosX - v2.mPosX) * (v0.mPosX - v2.mPosX) + (v0.mPosY - v2.mPosY) * (v0.mPosY - v2.mPosY)); double dist3 = Math.sqrt((v0.mPosX - v3.mPosX) * (v0.mPosX - v3.mPosX) + (v0.mPosY - v3.mPosY) * (v0.mPosY - v3.mPosY)); if (dist2 > dist3) { lines[1][1] = 3; lines[2][1] = 2; } } mVerticesCountFront = 0; mVerticesCountBack = 0; if (DRAW_SHADOW) { mArrTempShadowVertices.addAll(mArrDropShadowVertices); mArrTempShadowVertices.addAll(mArrSelfShadowVertices); mArrDropShadowVertices.clear(); mArrSelfShadowVertices.clear(); } // Length of 'curl' curve. double curlLength = Math.PI * radius; // Calculate scan lines. mArrScanLines.clear(); if (mMaxCurlSplits > 0) { mArrScanLines.add((double) 0); } for (int i = 1; i < mMaxCurlSplits; ++i) { mArrScanLines.add((-curlLength * i) / (mMaxCurlSplits - 1)); } // As mRotatedVertices is ordered regarding x -coordinate, adding // this scan line produces scan area picking up vertices which are // rotated completely. One could say 'until infinity'. mArrScanLines.add(mArrRotatedVertices.get(3).mPosX - 1); // Start from right most vertex. Pretty much the same as first scan area // is starting from 'infinity'. double scanXmax = mArrRotatedVertices.get(0).mPosX + 1; for (int i = 0; i < mArrScanLines.size(); ++i) { // Once we have scanXmin and scanXmax we have a scan area to start // working with. double scanXmin = mArrScanLines.get(i); // First iterate 'original' rectangle vertices within scan area. for (int j = 0; j < mArrRotatedVertices.size(); ++j) { Vertex v = mArrRotatedVertices.get(j); // Test if vertex lies within this scan area. if (v.mPosX >= scanXmin && v.mPosX <= scanXmax) { // Pop out a vertex from temp vertices. Vertex n = mArrTempVertices.remove(0); n.set(v); // This is done solely for triangulation reasons. Given a // rotated rectangle it has max 2 vertices having // intersection. Array<Vertex> intersections = getIntersections( mArrRotatedVertices, lines, n.mPosX); // In a sense one could say we're adding vertices always in // two, positioned at the ends of intersecting line. And for // triangulation to work properly they are added based on y // -coordinate. And this if-else is doing it for us. if (1 == intersections.size() && intersections.get(0).mPosY > v.mPosY) { // In case intersecting vertex is higher add it first. mArrOutputVertices.addAll(intersections); mArrOutputVertices.add(n); } else if (intersections.size() <= 1) { // Otherwise add original vertex first. mArrOutputVertices.add(n); mArrOutputVertices.addAll(intersections); } else { // There should never be more than 1 intersecting // vertex. But if it happens as a fallback simply skip // everything. mArrTempVertices.add(n); mArrTempVertices.addAll(intersections); } } } // Search for scan line intersections. Array<Vertex> intersections = getIntersections(mArrRotatedVertices, lines, scanXmin); // We expect to get 0 or 2 vertices. In rare cases there's only one // but in general given a scan line intersecting rectangle there // should be 2 intersecting vertices. if (2 == intersections.size()) { // There were two intersections, add them based on y // -coordinate, higher first, lower last. Vertex v1 = intersections.get(0); Vertex v2 = intersections.get(1); if (v1.mPosY < v2.mPosY) { mArrOutputVertices.add(v2); mArrOutputVertices.add(v1); } else { mArrOutputVertices.addAll(intersections); } } else if (0 != intersections.size()) { // This happens in a case in which there is a original vertex // exactly at scan line or something went very much wrong if // there are 3+ vertices. What ever the reason just return the // vertices to temp vertices for later use. In former case it // was handled already earlier once iterating through // mRotatedVertices, in latter case it's better to avoid doing // anything with them. mArrTempVertices.addAll(intersections); } // Add vertices found during this iteration to vertex etc buffers. while (mArrOutputVertices.size() > 0) { Vertex v = mArrOutputVertices.remove(0); mArrTempVertices.add(v); // Local texture front-facing flag. boolean textureFront; // Untouched vertices. if (0 == i) { textureFront = true; mVerticesCountFront++; } // 'Completely' rotated vertices. else if (i == mArrScanLines.size() - 1 || 0 == curlLength) { v.mPosX = -(curlLength + v.mPosX); v.mPosZ = 2 * radius; v.mPenumbraX = -v.mPenumbraX; textureFront = false; mVerticesCountBack++; } // Vertex lies within 'curl'. else { // Even though it's not obvious from the if-else clause, // here v.mPosX is between [-curlLength, 0]. And we can do // calculations around a half cylinder. double rotY = Math.PI * (v.mPosX / curlLength); v.mPosX = radius * Math.sin(rotY); v.mPosZ = radius - (radius * Math.cos(rotY)); v.mPenumbraX *= Math.cos(rotY); // Map color multiplier to [.1f, 1f] range. .sin(rotY) + // 1)); if (v.mPosZ >= radius) { textureFront = false; mVerticesCountBack++; } else { textureFront = true; mVerticesCountFront++; } } // We use local textureFront for flipping backside texture // locally. Plus additionally if mesh is in flip texture mode, // we'll make the procedure "backwards". Also, until this point, // texture coordinates are within [0, 1] range so we'll adjust // them to final texture coordinates too. if (textureFront != mFlipTexture) { v.mTexX *= mTextureRectFront.right; v.mTexY *= mTextureRectFront.bottom; v.mColor = mTexturePage.getColor(CurlPage.SIDE_FRONT); } else { v.mTexX *= mTextureRectBack.right; v.mTexY *= mTextureRectBack.bottom; v.mColor = mTexturePage.getColor(CurlPage.SIDE_BACK); } // Move vertex back to 'world' coordinates. v.rotateZ(curlAngle); v.translate(curlPos.x, curlPos.y); addVertex(v); // Drop shadow is cast 'behind' the curl. if (DRAW_SHADOW && v.mPosZ > 0 && v.mPosZ <= radius) { ShadowVertex sv = mArrTempShadowVertices.remove(0); sv.mPosX = v.mPosX; sv.mPosY = v.mPosY; sv.mPosZ = v.mPosZ; sv.mPenumbraX = (v.mPosZ / 2) * -curlDir.x; sv.mPenumbraY = (v.mPosZ / 2) * -curlDir.y; sv.mPenumbraColor = v.mPosZ / radius; int idx = (mArrDropShadowVertices.size() + 1) / 2; mArrDropShadowVertices.add(idx, sv); } // Self shadow is cast partly over mesh. if (DRAW_SHADOW && v.mPosZ > radius) { ShadowVertex sv = mArrTempShadowVertices.remove(0); sv.mPosX = v.mPosX; sv.mPosY = v.mPosY; sv.mPosZ = v.mPosZ; sv.mPenumbraX = ((v.mPosZ - radius) / 3) * v.mPenumbraX; sv.mPenumbraY = ((v.mPosZ - radius) / 3) * v.mPenumbraY; sv.mPenumbraColor = (v.mPosZ - radius) / (2 * radius); int idx = (mArrSelfShadowVertices.size() + 1) / 2; mArrSelfShadowVertices.add(idx, sv); } } // Switch scanXmin as scanXmax for next iteration. scanXmax = scanXmin; } mBufVertices.position(0); mBufColors.position(0); if (DRAW_TEXTURE) { mBufTexCoords.position(0); } // Add shadow Vertices. if (DRAW_SHADOW) { mBufShadowColors.position(0); mBufShadowVertices.position(0); mDropShadowCount = 0; for (int i = 0; i < mArrDropShadowVertices.size(); ++i) { ShadowVertex sv = mArrDropShadowVertices.get(i); mBufShadowVertices.put((float) sv.mPosX); mBufShadowVertices.put((float) sv.mPosY); mBufShadowVertices.put((float) sv.mPosZ); mBufShadowVertices.put((float) (sv.mPosX + sv.mPenumbraX)); mBufShadowVertices.put((float) (sv.mPosY + sv.mPenumbraY)); mBufShadowVertices.put((float) sv.mPosZ); for (int j = 0; j < 4; ++j) { double color = SHADOW_OUTER_COLOR[j] + (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j]) * sv.mPenumbraColor; mBufShadowColors.put((float) color); } mBufShadowColors.put(SHADOW_OUTER_COLOR); mDropShadowCount += 2; } mSelfShadowCount = 0; for (int i = 0; i < mArrSelfShadowVertices.size(); ++i) { ShadowVertex sv = mArrSelfShadowVertices.get(i); mBufShadowVertices.put((float) sv.mPosX); mBufShadowVertices.put((float) sv.mPosY); mBufShadowVertices.put((float) sv.mPosZ); mBufShadowVertices.put((float) (sv.mPosX + sv.mPenumbraX)); mBufShadowVertices.put((float) (sv.mPosY + sv.mPenumbraY)); mBufShadowVertices.put((float) sv.mPosZ); for (int j = 0; j < 4; ++j) { double color = SHADOW_OUTER_COLOR[j] + (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j]) * sv.mPenumbraColor; mBufShadowColors.put((float) color); } mBufShadowColors.put(SHADOW_OUTER_COLOR); mSelfShadowCount += 2; } mBufShadowColors.position(0); mBufShadowVertices.position(0); } } /** * Calculates intersections for given scan line. */ private Array<Vertex> getIntersections(Array<Vertex> vertices, int[][] lineIndices, double scanX) { mArrIntersections.clear(); // Iterate through rectangle lines each re-presented as a pair of // vertices. for (int j = 0; j < lineIndices.length; j++) { Vertex v1 = vertices.get(lineIndices[j][0]); Vertex v2 = vertices.get(lineIndices[j][1]); // Here we expect that v1.mPosX >= v2.mPosX and wont do intersection // test the opposite way. if (v1.mPosX > scanX && v2.mPosX < scanX) { // There is an intersection, calculate coefficient telling 'how // far' scanX is from v2. double c = (scanX - v2.mPosX) / (v1.mPosX - v2.mPosX); Vertex n = mArrTempVertices.remove(0); n.set(v2); n.mPosX = scanX; n.mPosY += (v1.mPosY - v2.mPosY) * c; if (DRAW_TEXTURE) { n.mTexX += (v1.mTexX - v2.mTexX) * c; n.mTexY += (v1.mTexY - v2.mTexY) * c; } if (DRAW_SHADOW) { n.mPenumbraX += (v1.mPenumbraX - v2.mPenumbraX) * c; n.mPenumbraY += (v1.mPenumbraY - v2.mPenumbraY) * c; } mArrIntersections.add(n); } } return mArrIntersections; } /** * Getter for textures page for this mesh. */ public synchronized CurlPage getTexturePage() { return mTexturePage; } /** * Renders our page curl mesh. */ public synchronized void onDrawFrame(GL10 gl) { // First allocate texture if there is not one yet. if (DRAW_TEXTURE && null == mTextureIds) { // Generate texture. mTextureIds = new int[2]; gl.glGenTextures(2, mTextureIds, 0); for (int textureId : mTextureIds) { // Set texture attributes. gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId); if (ConfigHandler.CURL_GRAPHIC_QUALITY) { /** With MultisampleConfig Chooser */ gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); } else { /** Without MultisampleConfigChooser */ gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST); } gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE); gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE); } } if (DRAW_TEXTURE && mTexturePage.isTexturesChanged()) { gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]); Bitmap texture = mTexturePage.getTexture(mTextureRectFront, CurlPage.SIDE_FRONT); if (null != texture) GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0); // NITRO TEMP FIX//// // Don't Recycle Image // texture.recycle(); // //////////////////// mTextureBack = mTexturePage.hasBackTexture(); if (mTextureBack) { gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]); texture = mTexturePage.getTexture(mTextureRectBack, CurlPage.SIDE_BACK); if (null != texture) GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0); // NITRO TEMP FIX//// // Don't Recycle Image // texture.recycle(); // //////////////////// } else { mTextureRectBack.set(mTextureRectFront); } // NITRO TEMP FIX//// // Don't Recycle Image // mTexturePage.recycle(); mTexturePage.getCleanBitmap(); // ////////NITRO BLINK FIX 12.12.2012. // reset(); // ////////////////////////////////// } // Some 'global' settings. gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); // Drop shadow drawing is done temporarily here to hide some // problems with its calculation. if (DRAW_SHADOW) { gl.glDisable(GL10.GL_TEXTURE_2D); gl.glEnable(GL10.GL_BLEND); gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); gl.glEnableClientState(GL10.GL_COLOR_ARRAY); gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufShadowColors); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufShadowVertices); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mDropShadowCount); gl.glDisableClientState(GL10.GL_COLOR_ARRAY); gl.glDisable(GL10.GL_BLEND); } if (DRAW_TEXTURE) { gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mBufTexCoords); } gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufVertices); // Enable color array. gl.glEnableClientState(GL10.GL_COLOR_ARRAY); gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufColors); // Draw front facing blank vertices. gl.glDisable(GL10.GL_TEXTURE_2D); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mVerticesCountFront); // Draw front facing texture. if (DRAW_TEXTURE) { // gl.glEnable(GL10.GL_BLEND); gl.glEnable(GL10.GL_TEXTURE_2D); if (!mFlipTexture || !mTextureBack) { gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]); } else { gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]); } // gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_DST_COLOR); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mVerticesCountFront); // gl.glDisable(GL10.GL_BLEND); gl.glDisable(GL10.GL_TEXTURE_2D); } int backStartIdx = Math.max(0, mVerticesCountFront - 2); int backCount = mVerticesCountFront + mVerticesCountBack - backStartIdx; // Draw back facing blank vertices. gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, backStartIdx, backCount); // Draw back facing texture. if (DRAW_TEXTURE) { // gl.glEnable(GL10.GL_BLEND); gl.glEnable(GL10.GL_TEXTURE_2D); if (mFlipTexture || !mTextureBack) { gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]); } else { gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]); } // gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_DST_COLOR); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, backStartIdx, backCount); // gl.glDisable(GL10.GL_BLEND); gl.glDisable(GL10.GL_TEXTURE_2D); } // Disable textures and color array. gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); gl.glDisableClientState(GL10.GL_COLOR_ARRAY); if (DRAW_SHADOW) { gl.glEnable(GL10.GL_BLEND); gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA); gl.glEnableClientState(GL10.GL_COLOR_ARRAY); gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufShadowColors); gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufShadowVertices); gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, mDropShadowCount, mSelfShadowCount); gl.glDisableClientState(GL10.GL_COLOR_ARRAY); gl.glDisable(GL10.GL_BLEND); } gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); } /** * Resets mesh to 'initial' state. Meaning this mesh will draw a plain * textured rectangle after call to this method. */ public synchronized void reset() { mBufVertices.position(0); mBufColors.position(0); if (DRAW_TEXTURE) { mBufTexCoords.position(0); } for (int i = 0; i < 4; ++i) { Vertex tmp = mArrTempVertices.get(0); tmp.set(mRectangle[i]); if (mFlipTexture) { tmp.mTexX *= mTextureRectBack.right; tmp.mTexY *= mTextureRectBack.bottom; tmp.mColor = mTexturePage.getColor(CurlPage.SIDE_BACK); } else { tmp.mTexX *= mTextureRectFront.right; tmp.mTexY *= mTextureRectFront.bottom; tmp.mColor = mTexturePage.getColor(CurlPage.SIDE_FRONT); } addVertex(tmp); } mVerticesCountFront = 4; mVerticesCountBack = 0; mBufVertices.position(0); mBufColors.position(0); if (DRAW_TEXTURE) { mBufTexCoords.position(0); } mDropShadowCount = 0; mSelfShadowCount = 0; } /** * Resets allocated texture id forcing creation of new one. After calling * this method you most likely want to set bitmap too as it's lost. This * method should be called only once e.g GL context is re-created as this * method does not release previous texture id, only makes sure new one is * requested on next render. */ public synchronized void resetTexture() { mTextureIds = null; } /** * If true, flips texture sideways. */ public synchronized void setFlipTexture(boolean flipTexture) { mFlipTexture = flipTexture; if (flipTexture) { setTexCoords(1f, 0f, 0f, 1f); } else { setTexCoords(0f, 0f, 1f, 1f); } } /** * Update mesh bounds. */ public void setRect(RectF r) { mRectangle[0].mPosX = r.left; mRectangle[0].mPosY = r.top; mRectangle[1].mPosX = r.left; mRectangle[1].mPosY = r.bottom; mRectangle[2].mPosX = r.right; mRectangle[2].mPosY = r.top; mRectangle[3].mPosX = r.right; mRectangle[3].mPosY = r.bottom; } /** * Sets texture coordinates to mRectangle vertices. */ private synchronized void setTexCoords(float left, float top, float right, float bottom) { mRectangle[0].mTexX = left; mRectangle[0].mTexY = top; mRectangle[1].mTexX = left; mRectangle[1].mTexY = bottom; mRectangle[2].mTexX = right; mRectangle[2].mTexY = top; mRectangle[3].mTexX = right; mRectangle[3].mTexY = bottom; } /** * Simple fixed size array implementation. */ private class Array<T> { private Object[] mArray; private int mCapacity; private int mSize; public Array(int capacity) { mCapacity = capacity; mArray = new Object[capacity]; } public void add(int index, T item) { if (index < 0 || index > mSize || mSize >= mCapacity) { throw new IndexOutOfBoundsException(); } for (int i = mSize; i > index; --i) { mArray[i] = mArray[i - 1]; } mArray[index] = item; ++mSize; } public void add(T item) { if (mSize >= mCapacity) { throw new IndexOutOfBoundsException(); } mArray[mSize++] = item; } public void addAll(Array<T> array) { if (mSize + array.size() > mCapacity) { throw new IndexOutOfBoundsException(); } for (int i = 0; i < array.size(); ++i) { mArray[mSize++] = array.get(i); } } public void clear() { mSize = 0; } @SuppressWarnings("unchecked") public T get(int index) { if (index < 0 || index >= mSize) { throw new IndexOutOfBoundsException(); } return (T) mArray[index]; } @SuppressWarnings("unchecked") public T remove(int index) { if (index < 0 || index >= mSize) { throw new IndexOutOfBoundsException(); } T item = (T) mArray[index]; for (int i = index; i < mSize - 1; ++i) { mArray[i] = mArray[i + 1]; } --mSize; return item; } public int size() { return mSize; } } /** * Holder for shadow vertex information. */ private class ShadowVertex { public double mPenumbraColor; public double mPenumbraX; public double mPenumbraY; public double mPosX; public double mPosY; public double mPosZ; } /** * Holder for vertex information. */ private class Vertex { public int mColor; public float mColorFactor; public double mPenumbraX; public double mPenumbraY; public double mPosX; public double mPosY; public double mPosZ; public double mTexX; public double mTexY; public Vertex() { mPosX = mPosY = mPosZ = mTexX = mTexY = 0; mColorFactor = 1.0f; } public void rotateZ(double theta) { double cos = Math.cos(theta); double sin = Math.sin(theta); double x = mPosX * cos + mPosY * sin; double y = mPosX * -sin + mPosY * cos; mPosX = x; mPosY = y; double px = mPenumbraX * cos + mPenumbraY * sin; double py = mPenumbraX * -sin + mPenumbraY * cos; mPenumbraX = px; mPenumbraY = py; } public void set(Vertex vertex) { mPosX = vertex.mPosX; mPosY = vertex.mPosY; mPosZ = vertex.mPosZ; mTexX = vertex.mTexX; mTexY = vertex.mTexY; mPenumbraX = vertex.mPenumbraX; mPenumbraY = vertex.mPenumbraY; mColor = vertex.mColor; mColorFactor = vertex.mColorFactor; } public void translate(double dx, double dy) { mPosX += dx; mPosY += dy; } } }