优化
This commit is contained in:
@@ -1,957 +0,0 @@
|
||||
package io.legado.app.ui.book.read.page.delegate.curl;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.RectF;
|
||||
import android.opengl.GLUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
/**
|
||||
* Class implementing actual curl/page rendering.
|
||||
*
|
||||
* @author harism
|
||||
*/
|
||||
public class CurlMesh {
|
||||
|
||||
// Flag for rendering some lines used for developing. Shows
|
||||
// curl position and one for the direction from the
|
||||
// position given. Comes handy once playing around with different
|
||||
// ways for following pointer.
|
||||
private static final boolean DRAW_CURL_POSITION = false;
|
||||
// Flag for drawing polygon outlines. Using this flag crashes on emulator
|
||||
// due to reason unknown to me. Leaving it here anyway as seeing polygon
|
||||
// outlines gives good insight how original rectangle is divided.
|
||||
private static final boolean DRAW_POLYGON_OUTLINES = false;
|
||||
// Flag for enabling shadow rendering.
|
||||
private static final boolean DRAW_SHADOW = true;
|
||||
// Flag for texture rendering. While this is likely something you
|
||||
// don't want to do it's been used for development purposes as texture
|
||||
// rendering is rather slow on emulator.
|
||||
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, .5f};
|
||||
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 final Array<ShadowVertex> mArrDropShadowVertices;
|
||||
private final Array<Vertex> mArrIntersections;
|
||||
private final Array<Vertex> mArrOutputVertices;
|
||||
private final Array<Vertex> mArrRotatedVertices;
|
||||
private final Array<Double> mArrScanLines;
|
||||
private final Array<ShadowVertex> mArrSelfShadowVertices;
|
||||
private final Array<ShadowVertex> mArrTempShadowVertices;
|
||||
private final Array<Vertex> mArrTempVertices;
|
||||
|
||||
// Buffers for feeding rasterizer.
|
||||
private final FloatBuffer mBufColors;
|
||||
private FloatBuffer mBufCurlPositionLines;
|
||||
private final FloatBuffer mBufShadowColors;
|
||||
private final FloatBuffer mBufShadowVertices;
|
||||
private final FloatBuffer mBufTexCoords;
|
||||
private final FloatBuffer mBufVertices;
|
||||
|
||||
private int mCurlPositionLinesCount;
|
||||
private int mDropShadowCount;
|
||||
|
||||
// Boolean for 'flipping' texture sideways.
|
||||
private boolean mFlipTexture = false;
|
||||
// Maximum number of split lines used for creating a curl.
|
||||
private final 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 = mRectangle[1].mPenumbraX = mRectangle[1].mPenumbraY = mRectangle[3].mPenumbraY = -1;
|
||||
mRectangle[0].mPenumbraY = mRectangle[2].mPenumbraX = mRectangle[2].mPenumbraY = mRectangle[3].mPenumbraX = 1;
|
||||
|
||||
if (DRAW_CURL_POSITION) {
|
||||
mCurlPositionLinesCount = 3;
|
||||
ByteBuffer hvbb = ByteBuffer
|
||||
.allocateDirect(mCurlPositionLinesCount * 2 * 2 * 4);
|
||||
hvbb.order(ByteOrder.nativeOrder());
|
||||
mBufCurlPositionLines = hvbb.asFloatBuffer();
|
||||
mBufCurlPositionLines.position(0);
|
||||
}
|
||||
|
||||
// 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 = 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.
|
||||
if (DRAW_CURL_POSITION) {
|
||||
mBufCurlPositionLines.position(0);
|
||||
|
||||
mBufCurlPositionLines.put(curlPos.x);
|
||||
mBufCurlPositionLines.put(curlPos.y - 1.0f);
|
||||
mBufCurlPositionLines.put(curlPos.x);
|
||||
mBufCurlPositionLines.put(curlPos.y + 1.0f);
|
||||
mBufCurlPositionLines.put(curlPos.x - 1.0f);
|
||||
mBufCurlPositionLines.put(curlPos.y);
|
||||
mBufCurlPositionLines.put(curlPos.x + 1.0f);
|
||||
mBufCurlPositionLines.put(curlPos.y);
|
||||
|
||||
mBufCurlPositionLines.put(curlPos.x);
|
||||
mBufCurlPositionLines.put(curlPos.y);
|
||||
mBufCurlPositionLines.put(curlPos.x + curlDir.x * 2);
|
||||
mBufCurlPositionLines.put(curlPos.y + curlDir.y * 2);
|
||||
|
||||
mBufCurlPositionLines.position(0);
|
||||
}
|
||||
|
||||
// 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 (v.mPosX == v2.mPosX && 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}};
|
||||
{
|
||||
// TODO: There really has to be more 'easier' way of doing this -
|
||||
// not including extensive use of sqrt.
|
||||
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 = 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.
|
||||
// TODO: Revisit this code one day. There is room for optimization here.
|
||||
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.
|
||||
// TODO: Frankly speaking, can't remember why equality check was
|
||||
// added to both ends. Guessing it was somehow related to case
|
||||
// where radius=0f, which, given current implementation, could
|
||||
// be handled much more effectively anyway.
|
||||
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 (intersections.size() == 1
|
||||
&& 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 (intersections.size() == 2) {
|
||||
// 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 (intersections.size() != 0) {
|
||||
// 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 (i == 0) {
|
||||
textureFront = true;
|
||||
mVerticesCountFront++;
|
||||
}
|
||||
// 'Completely' rotated vertices.
|
||||
else if (i == mArrScanLines.size() - 1 || curlLength == 0) {
|
||||
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.
|
||||
v.mColorFactor = (float) (.1f + .9f * Math.sqrt(Math
|
||||
.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 && mTextureIds == null) {
|
||||
// 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);
|
||||
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.getTexturesChanged()) {
|
||||
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]);
|
||||
Bitmap texture = mTexturePage.getTexture(mTextureRectFront,
|
||||
CurlPage.SIDE_FRONT);
|
||||
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0);
|
||||
texture.recycle();
|
||||
|
||||
mTextureBack = mTexturePage.hasBackTexture();
|
||||
if (mTextureBack) {
|
||||
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]);
|
||||
texture = mTexturePage.getTexture(mTextureRectBack,
|
||||
CurlPage.SIDE_BACK);
|
||||
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0);
|
||||
texture.recycle();
|
||||
} else {
|
||||
mTextureRectBack.set(mTextureRectFront);
|
||||
}
|
||||
|
||||
mTexturePage.recycle();
|
||||
reset();
|
||||
}
|
||||
|
||||
// Some 'global' settings.
|
||||
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
|
||||
|
||||
// TODO: 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_ONE_MINUS_SRC_ALPHA);
|
||||
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_ONE_MINUS_SRC_ALPHA);
|
||||
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_POLYGON_OUTLINES) {
|
||||
gl.glEnable(GL10.GL_BLEND);
|
||||
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
|
||||
gl.glLineWidth(1.0f);
|
||||
gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f);
|
||||
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufVertices);
|
||||
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, mVerticesCountFront);
|
||||
gl.glDisable(GL10.GL_BLEND);
|
||||
}
|
||||
|
||||
if (DRAW_CURL_POSITION) {
|
||||
gl.glEnable(GL10.GL_BLEND);
|
||||
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
|
||||
gl.glLineWidth(1.0f);
|
||||
gl.glColor4f(1.0f, 0.5f, 0.5f, 1.0f);
|
||||
gl.glVertexPointer(2, GL10.GL_FLOAT, 0, mBufCurlPositionLines);
|
||||
gl.glDrawArrays(GL10.GL_LINES, 0, mCurlPositionLinesCount * 2);
|
||||
gl.glDisable(GL10.GL_BLEND);
|
||||
}
|
||||
|
||||
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 = 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 final Object[] mArray;
|
||||
private final 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package io.legado.app.ui.book.read.page.delegate.curl;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.RectF;
|
||||
|
||||
/**
|
||||
* Storage class for page textures, blend colors and possibly some other values
|
||||
* in the future.
|
||||
*
|
||||
* @author harism
|
||||
*/
|
||||
public class CurlPage {
|
||||
|
||||
public static final int SIDE_BACK = 2;
|
||||
public static final int SIDE_BOTH = 3;
|
||||
public static final int SIDE_FRONT = 1;
|
||||
|
||||
private int mColorBack;
|
||||
private int mColorFront;
|
||||
private Bitmap mTextureBack;
|
||||
private Bitmap mTextureFront;
|
||||
private boolean mTexturesChanged;
|
||||
|
||||
/**
|
||||
* Default constructor.
|
||||
*/
|
||||
public CurlPage() {
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for color.
|
||||
*/
|
||||
public int getColor(int side) {
|
||||
switch (side) {
|
||||
case SIDE_FRONT:
|
||||
return mColorFront;
|
||||
default:
|
||||
return mColorBack;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the next highest power of two for a given integer.
|
||||
*/
|
||||
private int getNextHighestPO2(int n) {
|
||||
n -= 1;
|
||||
n = n | (n >> 1);
|
||||
n = n | (n >> 2);
|
||||
n = n | (n >> 4);
|
||||
n = n | (n >> 8);
|
||||
n = n | (n >> 16);
|
||||
n = n | (n >> 32);
|
||||
return n + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates nearest power of two sized Bitmap for give Bitmap. Returns this
|
||||
* new Bitmap using default return statement + original texture coordinates
|
||||
* are stored into RectF.
|
||||
*/
|
||||
private Bitmap getTexture(Bitmap bitmap, RectF textureRect) {
|
||||
// Bitmap original size.
|
||||
int w = bitmap.getWidth();
|
||||
int h = bitmap.getHeight();
|
||||
// Bitmap size expanded to next power of two. This is done due to
|
||||
// the requirement on many devices, texture width and height should
|
||||
// be power of two.
|
||||
int newW = getNextHighestPO2(w);
|
||||
int newH = getNextHighestPO2(h);
|
||||
|
||||
// TODO: Is there another way to create a bigger Bitmap and copy
|
||||
// original Bitmap to it more efficiently? Immutable bitmap anyone?
|
||||
Bitmap bitmapTex = Bitmap.createBitmap(newW, newH, bitmap.getConfig());
|
||||
Canvas c = new Canvas(bitmapTex);
|
||||
c.drawBitmap(bitmap, 0, 0, null);
|
||||
|
||||
// Calculate final texture coordinates.
|
||||
float texX = (float) w / newW;
|
||||
float texY = (float) h / newH;
|
||||
textureRect.set(0f, 0f, texX, texY);
|
||||
|
||||
return bitmapTex;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for textures. Creates Bitmap sized to nearest power of two, copies
|
||||
* original Bitmap into it and returns it. RectF given as parameter is
|
||||
* filled with actual texture coordinates in this new upscaled texture
|
||||
* Bitmap.
|
||||
*/
|
||||
public Bitmap getTexture(RectF textureRect, int side) {
|
||||
switch (side) {
|
||||
case SIDE_FRONT:
|
||||
return getTexture(mTextureFront, textureRect);
|
||||
default:
|
||||
return getTexture(mTextureBack, textureRect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if textures have changed.
|
||||
*/
|
||||
public boolean getTexturesChanged() {
|
||||
return mTexturesChanged;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if back siding texture exists and it differs from front
|
||||
* facing one.
|
||||
*/
|
||||
public boolean hasBackTexture() {
|
||||
return !mTextureFront.equals(mTextureBack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycles and frees underlying Bitmaps.
|
||||
*/
|
||||
public void recycle() {
|
||||
if (mTextureFront != null) {
|
||||
mTextureFront.recycle();
|
||||
}
|
||||
mTextureFront = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
|
||||
mTextureFront.eraseColor(mColorFront);
|
||||
if (mTextureBack != null) {
|
||||
mTextureBack.recycle();
|
||||
}
|
||||
mTextureBack = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
|
||||
mTextureBack.eraseColor(mColorBack);
|
||||
mTexturesChanged = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this CurlPage into its initial state.
|
||||
*/
|
||||
public void reset() {
|
||||
mColorBack = Color.WHITE;
|
||||
mColorFront = Color.WHITE;
|
||||
recycle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter blend color.
|
||||
*/
|
||||
public void setColor(int color, int side) {
|
||||
switch (side) {
|
||||
case SIDE_FRONT:
|
||||
mColorFront = color;
|
||||
break;
|
||||
case SIDE_BACK:
|
||||
mColorBack = color;
|
||||
break;
|
||||
default:
|
||||
mColorFront = mColorBack = color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for textures.
|
||||
*/
|
||||
public void setTexture(Bitmap texture, int side) {
|
||||
if (texture == null) {
|
||||
texture = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
|
||||
if (side == SIDE_BACK) {
|
||||
texture.eraseColor(mColorBack);
|
||||
} else {
|
||||
texture.eraseColor(mColorFront);
|
||||
}
|
||||
}
|
||||
switch (side) {
|
||||
case SIDE_FRONT:
|
||||
if (mTextureFront != null)
|
||||
mTextureFront.recycle();
|
||||
mTextureFront = texture;
|
||||
break;
|
||||
case SIDE_BACK:
|
||||
if (mTextureBack != null)
|
||||
mTextureBack.recycle();
|
||||
mTextureBack = texture;
|
||||
break;
|
||||
case SIDE_BOTH:
|
||||
if (mTextureFront != null)
|
||||
mTextureFront.recycle();
|
||||
if (mTextureBack != null)
|
||||
mTextureBack.recycle();
|
||||
mTextureFront = mTextureBack = texture;
|
||||
break;
|
||||
}
|
||||
mTexturesChanged = true;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user