/* * 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; 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. 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); } } }