package io.nextop.demo.globaleye.wallpaper;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.opengl.GLU;
import android.opengl.GLUtils;
import android.os.AsyncTask;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import net.rbgrn.android.glwallpaperservice.GLWallpaperService;
import javax.microedition.khronos.opengles.GL10;
import java.io.BufferedInputStream;
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 java.util.concurrent.TimeUnit;
public class LightOnWater extends GLWallpaperService {
static final String TAG = "LightOnWater";
final Handler handler = new Handler();
@Nullable
Movement movement = null;
@Override
public void onCreate() {
super.onCreate();
new LoadMovement().execute();
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
public Engine onCreateEngine() {
return new LightOnWaterEngine();
}
static final class Movement {
final float ccx;
final float ccy;
final P[][] allPoints;
Movement(float ccx, float ccy, P[][] allPoints) {
this.ccx = ccx;
this.ccy = ccy;
this.allPoints = allPoints;
}
}
static final class P {
final float x;
final float y;
final float s;
final float q;
final float t;
final float r;
final float vx;
final float vy;
float rx;
float ry;
float rs;
final float[] sc = new float[4];
final float[] fc = new float[4];
boolean stroke;
boolean fill;
P(float x, float y, float s, float q, float t, float r) {
this.x = x;
this.y = y;
this.s = s;
this.q = q;
this.t = t;
this.r = r;
vx = (float) Math.cos(t);
vy = (float) Math.sin(t);
}
}
private final class LoadMovement extends AsyncTask<Void, Void, Movement> {
@Override
@Nullable
protected Movement doInBackground(Void... args) {
try {
BitInputStream bis = new BitInputStream(new BufferedInputStream(getAssets().open("points.bin")));
try {
// [ccx, 16 bits][ccy, 16 bits][# frames, 16 bits][frame]...
// [frame] = [# points 16 bits][point]...
// [point] = [x, 9 bits][y, 9 bits][s * 15, 7 bits][q * 15, 5 bits]
int ccx = bis.readBits(16);
int ccy = bis.readBits(16);
float hccx = ccx * 0.5f;
float hccy = ccy * 0.5f;
int c = bis.readBits(16);
P[][] allPoints = new P[c][];
for (int i = 0; i < c; ++i) {
int n = bis.readBits(16);
P[] points = new P[n];
allPoints[i] = points;
for (int j = 0; j < n; ++j) {
int x = bis.readBits(9);
int y = bis.readBits(9);
float s = bis.readBits(7) / 15.f;
float q = bis.readBits(5) / 15.f;
// convert to radial
float dx = x - hccx;
float dy = y - hccy;
float t = (float) Math.atan2(dy, dx);
float r = (float) Math.sqrt(dx * dx + dy * dy);
points[j] = new P(x, y, s, q, t, r);
}
}
return new Movement(ccx, ccy, allPoints);
} finally {
bis.close();
}
} catch (Exception e) {
Log.e(TAG, "LoadPoints#doInBackground", e);
return null;
}
}
@Override
protected void onPostExecute(Movement movement) {
LightOnWater.this.movement = movement;
}
}
private static final class BitInputStream {
private final InputStream is;
private int b = -1;
private int bi = 8;
BitInputStream(BufferedInputStream is) {
this.is = is;
}
private static final int[] masks = {
0xFF,
0x7F,
0x3F,
0x1F,
0x0F,
0x07,
0x03,
0x01
};
public int readBits(int n) throws IOException {
if (32 < n) {
throw new IllegalArgumentException();
}
int out = 0;
while (0 < n) {
if (8 <= bi) {
b = is.read();
if (b < 0) {
break;
}
bi = 0;
}
int a = 8 - bi;
int u = a < n ? a : n;
out <<= u;
out |= (b & masks[bi]) >>> (a - u);
bi += u;
n -= u;
}
return out;
}
public void close() throws Exception {
is.close();
}
}
/**
* image masks to be used as tinted textures to draw shapes (circles)
*/
private static final class Masks {
static Masks create(int s, float ts, float tStrokeWidth) {
Paint paint = new Paint();
paint.setColor(Color.argb(255, 255, 255, 255));
paint.setAntiAlias(true);
Canvas c;
float p = 4;
Bitmap fill = Bitmap.createBitmap(2 * s, 2 * s, Bitmap.Config.ARGB_8888);
c = new Canvas(fill);
paint.setStyle(Paint.Style.FILL);
c.drawCircle(s, s, s - p, paint);
Bitmap stroke = Bitmap.createBitmap(2 * s, 2 * s, Bitmap.Config.ARGB_8888);
c = new Canvas(stroke);
paint.setStyle(Paint.Style.STROKE);
// when sized at ts the stroke width should be tStrokeWidth
// strokeWidth / s = tStrokeWidth / ts
paint.setStrokeWidth(tStrokeWidth * s / ts);
c.drawCircle(s, s, s - p, paint);
return new Masks(fill, stroke);
}
final Bitmap circleFill;
final Bitmap circleStroke;
Masks(Bitmap circleFill, Bitmap circleStroke) {
this.circleFill = circleFill;
this.circleStroke = circleStroke;
}
}
final class LightOnWaterEngine extends GLEngine {
volatile boolean touch = false;
volatile float touchX = 0.f;
volatile float touchY = 0.f;
volatile float offsetX = 0.f;
volatile float offsetY = 0.f;
final Renderer renderer;
LightOnWaterEngine() {
renderer = new Renderer(Masks.create(128, 8.f, 1.f));
setRenderer(renderer);
setRenderMode(RENDERMODE_CONTINUOUSLY);
}
@Override
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
setTouchEventsEnabled(true);
}
@Override
public void onOffsetsChanged(float xOffset, float yOffset,
float xStep, float yStep, int xPixels, int yPixels) {
super.onOffsetsChanged(xOffset, yOffset, xStep, yStep, xPixels, yPixels);
offsetX = xPixels;
offsetY = yPixels;
}
@Override
public void onTouchEvent(MotionEvent event) {
super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
touch = true;
touchX = event.getX();
touchY = event.getY();
break;
case MotionEvent.ACTION_UP:
touch = false;
break;
default:
// ignore
break;
}
}
private class Renderer implements GLWallpaperService.Renderer {
final Masks masks;
int width = 0;
int height = 0;
long startNanos = 0L;
final float fps = 60.f;
final int tt = 2;
float frameCount;
float mmm = 0.f;
int fillTextureId;
int strokeTextureId;
FloatBuffer circleVertexes2d;
FloatBuffer circleTexCoords2d;
ShortBuffer circleIndexes;
Renderer(Masks masks) {
this.masks = masks;
}
void release() {
masks.circleFill.recycle();
masks.circleStroke.recycle();
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
this.width = width;
this.height = height;
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluOrtho2D(gl, 0, width, 0, height);
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}
@Override
public void onSurfaceCreated(GL10 gl, javax.microedition.khronos.egl.EGLConfig eglConfig) {
startNanos = System.nanoTime();
gl.glDisable(GL10.GL_DEPTH_TEST);
gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
fillTextureId = loadTexture(gl, masks.circleFill);
strokeTextureId = loadTexture(gl, masks.circleStroke);
circleVertexes2d = toBuffer(new float[]{
-1.f, 1.f,
-1.f, -1.f,
1.f, -1.f,
1.f, 1.f,
});
circleTexCoords2d = toBuffer(new float[]{
0.f, 0.f,
0.f, 1.f,
1.f, 1.f,
1.f, 0.f
});
circleIndexes = toBuffer(new short[]{
0, 1, 2,
0, 2, 3
});
}
@Override
public void onDrawFrame(GL10 gl) {
long nanos = System.nanoTime();
frameCount = TimeUnit.NANOSECONDS.toMillis(nanos - startNanos) * fps / TimeUnit.SECONDS.toMillis(1);
gl.glClearColor(0.f, 0.f, 0.f, 0.f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
if (touch) {
mmm = lerp(mmm, 1.f, 0.5f);
} else {
mmm = lerp(mmm, 0.f, 0.25f);
}
if (null != movement) {
drawMovement(gl, movement);
}
}
void drawMovement(GL10 gl, Movement m) {
float ku = (frameCount % tt) / tt;
int k = (int) (frameCount / tt) % m.allPoints.length;
P[] points = m.allPoints[k];
P[] ppoints = m.allPoints[(k + m.allPoints.length - 1) % m.allPoints.length];
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glVertexPointer(2, GL10.GL_FLOAT, 0, circleVertexes2d);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, circleTexCoords2d);
gl.glTexEnvf(GL10.GL_TEXTURE_ENV, GL10.GL_TEXTURE_ENV_MODE, GL10.GL_MODULATE);
gl.glColorMask(true, true, true, true);
layout(m.ccx, m.ccy, ppoints, 1.f, 0.f);
fill(gl, ppoints);
stroke(gl, ppoints);
layout(m.ccx, m.ccy, points, lerp(0.f, 1.f, ku), 0.8f);
fill(gl, points);
stroke(gl, points);
}
/**
* stores layout state into layout fields of points
*/
void layout(float ccx, float ccy, P[] points, float sf, float ff) {
float rCr = (float) Math.sqrt(ccx * ccx + ccy * ccy) / 2;
float rCx = width * 0.5f;
float rCy = height * 0.5f;
float rTr = Math.max(width, height) * 0.5f;
float rTfs = height * 0.3f;
float rTor = rTfs * 0.5f;
float xOff = offsetX * 0.8f;
float yOff = offsetY * 0.8f;
float[] a = new float[4];
float[] b = new float[4];
float ss = width / 800.f;
for (int i = points.length - 1; 0 <= i; --i) {
P p = points[i];
float rf = p.r / rCr;
// semi-spherical (or inverted) transform of r
float r = rTr * (float) Math.pow(rf, 0.95);
float af = p.q * (float) Math.pow(1.f - r / rTr, lerp(0.5f, 2.f, mmm));
float h = (float) Math.sqrt(rTr * rTr - r * r);
p.rs = p.s * lerp(0.5f * ss * lerp(1.f, 4.f, mmm), 4.f * ss * lerp(1.f, 1.7f, mmm), (float) Math.pow(h / rTr, 4.0));
p.rx = xOff + rCx + r * p.vx;
p.ry = yOff + rCy + r * p.vy;
p.stroke = 0 < sf;
if (p.stroke) {
float[] out = p.sc;
if (r < rTor) {
alphaScale(COLOR_PUPIL, sf, a);
alphaScale(COLOR_RETINA, sf * af, b);
lerpColor(a, b,
(float) Math.pow(map(r, 0, rTor, 0.f, 1.f), 2.f), out);
} else {
alphaScale(COLOR_RETINA, sf * af, a);
alphaScale(COLOR_OUTER, sf * af, b);
lerpColor(a, b,
(float) Math.pow(map(r, rTor, rTr, 0.f, 1.f), 0.5f), out);
}
}
p.fill = 0 < ff;
if (p.fill) {
float[] out = p.fc;
if (r < rTor) {
alphaScale(COLOR_PUPIL, ff, a);
alphaScale(COLOR_RETINA, ff * af, b);
lerpColor(a, b,
(float) Math.pow(map(r, 0, rTor, 0.f, 1.f), 2.f), out);
} else {
alphaScale(COLOR_RETINA, ff * af, a);
alphaScale(COLOR_OUTER, ff * af, b);
lerpColor(a, b,
(float) Math.pow(map(r, rTor, rTr, 0.f, 1.f), 0.5f), out);
}
}
}
}
private void stroke(GL10 gl, P[] points) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, strokeTextureId);
for (P p : points) {
if (p.stroke) {
p(gl, p.rx, p.ry, p.rs, p.fc);
}
}
}
private void fill(GL10 gl, P[] points) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, fillTextureId);
for (P p : points) {
if (p.fill) {
p(gl, p.rx, p.ry, p.rs, p.fc);
}
}
}
private void p(GL10 gl, float x, float y, float s, float[] c) {
gl.glPushMatrix();
{
gl.glTranslatef(x, y, 0.f);
gl.glScalef(s, s, 1.f);
gl.glColor4f(c[0], c[1], c[2], c[3]);
gl.glDrawElements(GL10.GL_TRIANGLES, circleIndexes.remaining(),
GL10.GL_UNSIGNED_SHORT, circleIndexes);
}
gl.glPopMatrix();
}
}
}
/////// GL UTILS ///////
static FloatBuffer toBuffer(float... values) {
return (FloatBuffer) ByteBuffer.allocateDirect(values.length * Float.SIZE / 8)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(values)
.position(0);
}
static ShortBuffer toBuffer(short... values) {
return (ShortBuffer) ByteBuffer.allocateDirect(values.length * Short.SIZE / 8)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(values)
.position(0);
}
static int loadTexture(GL10 gl, Bitmap b) {
int[] id = new int[1];
gl.glGenTextures(1, id, 0);
gl.glBindTexture(GL10.GL_TEXTURE_2D, id[0]);
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);
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);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, b, 0);
return id[0];
}
//////// PROCESSING + GRAPHICS UTILS ///////
static final float PI = (float) (Math.PI);
static final float TWO_PI = (float) (2 * Math.PI);
static float dist(float x1, float y1, float x2, float y2) {
float dx = x2 - x1;
float dy = y2 - y1;
return (float) Math.sqrt(dx * dx + dy * dy);
}
static float lerp(float start, float stop, float amt) {
return start + (stop - start) * amt;
}
static void lerpColor(float[] c1, float[] c2, float amt, float[] out) {
out[0] = lerp(c1[0], c2[0], amt);
out[1] = lerp(c1[1], c2[1], amt);
out[2] = lerp(c1[2], c2[2], amt);
out[3] = lerp(c1[3], c2[3], amt);
}
static float map(float value, float start1, float stop1, float start2, float stop2) {
return lerp(start2, stop2, (value - start1) / (stop1 - start1));
}
static void alphaScale(float[] c, float af, float[] out) {
out[0] = c[0];
out[1] = c[1];
out[2] = c[2];
out[3] = c[3] * af;
}
/////// COLORS ///////
/**
* convert [0, 255] components to [0, 1]
*/
static float[] rgbaNormalize(float[] rgba) {
return new float[]{
rgba[0] / 255.f,
rgba[1] / 255.f,
rgba[2] / 255.f,
rgba[3] / 255.f
};
}
static final float[] COLOR_PUPIL = rgbaNormalize(new float[]{240, 40, 120, 255});
static final float[] COLOR_RETINA = rgbaNormalize(new float[]{40, 170, 240, 255});
static final float[] COLOR_OUTER = rgbaNormalize(new float[]{220, 220, 220, 255});
}