blob: d87a5d97f3682700aecab71c82d893970971a4f7 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.nfc.beam;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.SurfaceTexture;
import android.opengl.GLUtils;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL10;
public class FireflyRenderer {
private static final String LOG_TAG = "NfcFireflyThread";
static final int NUM_FIREFLIES = 200;
static final float NEAR_CLIPPING_PLANE = 50f;
static final float FAR_CLIPPING_PLANE = 100f;
// All final variables below only need to be allocated once
// and can be reused between subsequent Beams
static final int[] sEglConfig = {
EGL10.EGL_RED_SIZE, 8,
EGL10.EGL_GREEN_SIZE, 8,
EGL10.EGL_BLUE_SIZE, 8,
EGL10.EGL_ALPHA_SIZE, 0,
EGL10.EGL_DEPTH_SIZE, 0,
EGL10.EGL_STENCIL_SIZE, 0,
EGL10.EGL_NONE
};
// Vertices for drawing a 32x32 rect
static final float mVertices[] = {
0.0f, 0.0f, 0.0f, // 0, Top Left
0.0f, 32.0f, 0.0f, // 1, Bottom Left
32.0f, 32.0f, 0.0f, // 2, Bottom Right
32.0f, 0.0f, 0.0f, // 3, Top Right
};
// Mapping coordinates for the texture
static final float mTextCoords[] = {
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f
};
// Connecting order (draws a square)
static final short[] mIndices = { 0, 1, 2, 0, 2, 3 };
final Context mContext;
// Buffer holding the vertices
final FloatBuffer mVertexBuffer;
// Buffer holding the indices
final ShortBuffer mIndexBuffer;
// Buffer holding the texture mapping coordinates
final FloatBuffer mTextureBuffer;
final Firefly[] mFireflies;
FireflyRenderThread mFireflyRenderThread;
// The surface to render the flies on, including width and height
SurfaceTexture mSurface;
int mDisplayWidth;
int mDisplayHeight;
public FireflyRenderer(Context context) {
mContext = context;
// First, build the vertex, texture and index buffers
ByteBuffer vbb = ByteBuffer.allocateDirect(mVertices.length * 4); // Float => 4 bytes
vbb.order(ByteOrder.nativeOrder());
mVertexBuffer = vbb.asFloatBuffer();
mVertexBuffer.put(mVertices);
mVertexBuffer.position(0);
ByteBuffer ibb = ByteBuffer.allocateDirect(mIndices.length * 2); // Short => 2 bytes
ibb.order(ByteOrder.nativeOrder());
mIndexBuffer = ibb.asShortBuffer();
mIndexBuffer.put(mIndices);
mIndexBuffer.position(0);
ByteBuffer tbb = ByteBuffer.allocateDirect(mTextCoords.length * 4);
tbb.order(ByteOrder.nativeOrder());
mTextureBuffer = tbb.asFloatBuffer();
mTextureBuffer.put(mTextCoords);
mTextureBuffer.position(0);
mFireflies = new Firefly[NUM_FIREFLIES];
for (int i = 0; i < NUM_FIREFLIES; i++) {
mFireflies[i] = new Firefly();
}
}
/**
* Starts rendering fireflies on the given surface.
* Must be called from the UI-thread.
*/
public void start(SurfaceTexture surface, int width, int height) {
mSurface = surface;
mDisplayWidth = width;
mDisplayHeight = height;
mFireflyRenderThread = new FireflyRenderThread();
mFireflyRenderThread.start();
}
/**
* Stops rendering fireflies.
* Must be called from the UI-thread.
*/
public void stop() {
if (mFireflyRenderThread != null) {
mFireflyRenderThread.finish();
try {
mFireflyRenderThread.join();
} catch (InterruptedException e) {
Log.e(LOG_TAG, "Couldn't wait for FireflyRenderThread.");
}
mFireflyRenderThread = null;
}
}
private class FireflyRenderThread extends Thread {
EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLConfig mEglConfig;
EGLContext mEglContext;
EGLSurface mEglSurface;
GL10 mGL;
// Holding the handle to the texture
int mTextureId;
// Read/written by multiple threads
volatile boolean mFinished;
@Override
public void run() {
if (!initGL()) {
Log.e(LOG_TAG, "Failed to initialize OpenGL.");
return;
}
loadStarTexture();
mGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mGL.glViewport(0, 0, mDisplayWidth, mDisplayHeight);
// make adjustments for screen ratio
mGL.glMatrixMode(GL10.GL_PROJECTION);
mGL.glLoadIdentity();
mGL.glFrustumf(-mDisplayWidth, mDisplayWidth, mDisplayHeight, -mDisplayHeight, NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE);
// Switch back to modelview
mGL.glMatrixMode(GL10.GL_MODELVIEW);
mGL.glLoadIdentity();
mGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
mGL.glDepthMask(true);
for (Firefly firefly : mFireflies) {
firefly.reset();
}
for (int i = 0; i < 3; i++) {
// Call eglSwapBuffers 3 times - this will allocate the necessary
// buffers, and make sure the animation looks smooth from the start.
mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
Log.e(LOG_TAG, "Could not swap buffers");
mFinished = true;
}
}
long startTime = System.currentTimeMillis();
while (!mFinished) {
long timeElapsedMs = System.currentTimeMillis() - startTime;
startTime = System.currentTimeMillis();
checkCurrent();
mGL.glClear(GL10.GL_COLOR_BUFFER_BIT);
mGL.glLoadIdentity();
mGL.glEnable(GL10.GL_TEXTURE_2D);
mGL.glEnable(GL10.GL_BLEND);
mGL.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
for (Firefly firefly : mFireflies) {
firefly.updatePositionAndScale(timeElapsedMs);
firefly.draw(mGL);
}
if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
Log.e(LOG_TAG, "Could not swap buffers");
mFinished = true;
}
long elapsed = System.currentTimeMillis() - startTime;
try {
Thread.sleep(Math.max(30 - elapsed, 0));
} catch (InterruptedException e) {
}
}
finishGL();
}
public void finish() {
mFinished = true;
}
void loadStarTexture() {
int[] textureIds = new int[1];
mGL.glGenTextures(1, textureIds, 0);
mTextureId = textureIds[0];
InputStream in = null;
try {
// Remember that both texture dimensions must be a power of 2!
in = mContext.getAssets().open("star.png");
Bitmap bitmap = BitmapFactory.decodeStream(in);
mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
bitmap.recycle();
} catch (IOException e) {
Log.e(LOG_TAG, "IOException opening assets.");
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) { }
}
}
}
private void checkCurrent() {
if (!mEglContext.equals(mEgl.eglGetCurrentContext()) ||
!mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) {
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throw new RuntimeException("eglMakeCurrent failed "
+ GLUtils.getEGLErrorString(mEgl.eglGetError()));
}
}
}
boolean initGL() {
// Initialize openGL engine
mEgl = (EGL10) EGLContext.getEGL();
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
Log.e(LOG_TAG, "eglGetDisplay failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
return false;
}
int[] version = new int[2];
if (!mEgl.eglInitialize(mEglDisplay, version)) {
Log.e(LOG_TAG, "eglInitialize failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
return false;
}
mEglConfig = chooseEglConfig();
if (mEglConfig == null) {
Log.e(LOG_TAG, "eglConfig not initialized.");
return false;
}
mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null);
mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null);
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
int error = mEgl.eglGetError();
Log.e(LOG_TAG,"createWindowSurface returned error " + Integer.toString(error));
return false;
}
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
Log.e(LOG_TAG, "eglMakeCurrent failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
return false;
}
mGL = (GL10) mEglContext.getGL();
return true;
}
private void finishGL() {
if (mEgl == null || mEglDisplay == null) {
// Nothing to free
return;
}
// Unbind the current surface and context from the display
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT);
if (mEglSurface != null) {
mEgl.eglDestroySurface(mEglDisplay, mEglSurface);
}
if (mEglContext != null) {
mEgl.eglDestroyContext(mEglDisplay, mEglContext);
}
}
private EGLConfig chooseEglConfig() {
int[] configsCount = new int[1];
EGLConfig[] configs = new EGLConfig[1];
if (!mEgl.eglChooseConfig(mEglDisplay, sEglConfig, configs, 1, configsCount)) {
throw new IllegalArgumentException("eglChooseConfig failed " +
GLUtils.getEGLErrorString(mEgl.eglGetError()));
} else if (configsCount[0] > 0) {
return configs[0];
}
return null;
}
}
private class Firefly {
static final float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size
static final float SPEED = .5f;
float mX; // between -mDisplayHeight and mDisplayHeight
float mY; // between -mDisplayWidth and mDisplayWidth
float mZ; // between 0.0 (near) and 1.0 (far)
float mZ0;
float mT;
float mScale;
float mAlpha;
public Firefly() {
}
void reset() {
mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth;
mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight;
mZ0 = mZ = (float) (Math.random()) * 2 - 1;
mT = 0f;
mScale = 1.5f;
mAlpha = 0f;
}
public void updatePositionAndScale(long timeElapsedMs) {
mT += timeElapsedMs;
mZ = mZ0 + mT/1000f * SPEED;
mAlpha = 1f-mZ;
if(mZ > 1.0) reset();
}
public void draw(GL10 gl) {
gl.glLoadIdentity();
// Counter clockwise winding
gl.glFrontFace(GL10.GL_CCW);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer);
gl.glTranslatef(mX, mY, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE));
gl.glColor4f(1, 1, 1, mAlpha);
// scale around center
gl.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0);
gl.glScalef(mScale, mScale, 0);
gl.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0);
gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT,
mIndexBuffer);
gl.glColor4f(1, 1, 1, 1);
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
}
}