|
@@ -18,13 +18,34 @@ import android.opengl.GLUtils;
|
|
|
import android.opengl.Matrix;
|
|
|
import android.util.Log;
|
|
|
|
|
|
+/**
|
|
|
+ * @author Fabio Arnold
|
|
|
+ *
|
|
|
+ * ThreatIndicatorGLRenderer
|
|
|
+ * This class is responsible for drawing an animation representing the current threat level.
|
|
|
+ * Use the method setThreatLevel to set the state (0 to 3).
|
|
|
+ */
|
|
|
+
|
|
|
public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
- private static int threatLevel = 0;
|
|
|
+ public static final int kThreatLevelNotMonitoring = 0;
|
|
|
+ public static final int kThreatLevelNoThreat = 1;
|
|
|
+ public static final int kThreatLevelPastThreat = 2;
|
|
|
+ public static final int kThreatLevelPersistentThreat = 3;
|
|
|
|
|
|
+ /**
|
|
|
+ * Set the threat level which should be indicated
|
|
|
+ * @param level 0 to 3 but better use the constants
|
|
|
+ */
|
|
|
public static void setThreatLevel(int level) {
|
|
|
- threatLevel = level;
|
|
|
+ assert(level >= 0 && level <= 3);
|
|
|
+ nextThreatLevel = level;
|
|
|
+ Log.i("threat indicator", "next threat level = " + level);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Match the background color of the view holding this renderer
|
|
|
+ * @param color 32 bit integer encoding the color
|
|
|
+ */
|
|
|
public static void setBackgroundColor(int color) {
|
|
|
backgroundColor[0] = (float)Color.red(color) / 255.0f;
|
|
|
backgroundColor[1] = (float)Color.green(color) / 255.0f;
|
|
@@ -32,12 +53,13 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
}
|
|
|
private static float[] backgroundColor = new float[3];
|
|
|
|
|
|
- public static AssetManager assets;
|
|
|
+ public static AssetManager assets = null; // needs to be set by the Activity using this class
|
|
|
|
|
|
private int width;
|
|
|
private int height;
|
|
|
private float aspectRatio;
|
|
|
|
|
|
+ // OpenGL data
|
|
|
private int program;
|
|
|
private float [] modelview;
|
|
|
private float [] projection;
|
|
@@ -47,13 +69,24 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
private AnimatedMesh beeMesh = null;
|
|
|
private int androidTexture;
|
|
|
private int beeTexture;
|
|
|
+
|
|
|
+ // threat state
|
|
|
+ private static int nextThreatLevel = kThreatLevelNoThreat;
|
|
|
+
|
|
|
+ private int currentThreatLevel = kThreatLevelNoThreat;
|
|
|
+ private int targetThreatLevel = kThreatLevelNoThreat;
|
|
|
+ private float threatLevelTransition = 1.0f; // 1.0 means transition is complete
|
|
|
|
|
|
- public ThreatIndicatorGLRenderer() {}
|
|
|
+ private long startTimeMillis; // for animation
|
|
|
+ public ThreatIndicatorGLRenderer() {
|
|
|
+ startTimeMillis = System.currentTimeMillis();
|
|
|
+ }
|
|
|
|
|
|
+ /**
|
|
|
+ * Initialization will be called after GL context is created and is current
|
|
|
+ */
|
|
|
public void onSurfaceCreated(GL10 arg0, EGLConfig arg1) {
|
|
|
- //GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
|
|
|
GLES20.glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], 1.0f);
|
|
|
- // GLES20.glClearColor(0.3f, 0.3f, 0.3f, 1.0f); // dark background
|
|
|
GLES20.glEnable(GLES20.GL_DEPTH_TEST);
|
|
|
GLES20.glEnable(GLES20.GL_CULL_FACE);
|
|
|
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
|
|
@@ -61,6 +94,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
try {
|
|
|
InputStream is = assets.open("meshes/android.amh");
|
|
|
androidMesh = new AnimatedMesh(is);
|
|
|
+ androidMesh.startAction("greet", false, false);
|
|
|
} catch (IOException e) {
|
|
|
Log.e("gl", "Couldn't open android mesh");
|
|
|
}
|
|
@@ -68,6 +102,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
try {
|
|
|
InputStream is = assets.open("meshes/bee.amh");
|
|
|
beeMesh = new AnimatedMesh(is);
|
|
|
+ beeMesh.startAction("bee_armatureAct", true, false);
|
|
|
} catch (IOException e) {
|
|
|
Log.e("gl", "Couldn't open bee mesh");
|
|
|
}
|
|
@@ -78,6 +113,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
projection = new float[16];
|
|
|
mvp = new float[16];
|
|
|
|
|
|
+ // default shader
|
|
|
String vertexSource = "attribute vec3 position; void main() {gl_Position = vec4(position, 1.0);}";
|
|
|
String fragmentSource = "void main() {gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);}";
|
|
|
try {
|
|
@@ -91,63 +127,148 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
}
|
|
|
program = loadProgram(vertexSource, fragmentSource);
|
|
|
}
|
|
|
-
|
|
|
- static float beeAnimation = 0.0f;
|
|
|
-
|
|
|
- private float[] mixColor(float alpha, float[] color1, float[] color2) {
|
|
|
- float[] color3 = new float[4];
|
|
|
- color3[0] = (1.0f - alpha) * color1[0] + alpha * color2[0];
|
|
|
- color3[1] = (1.0f - alpha) * color1[1] + alpha * color2[1];
|
|
|
- color3[2] = (1.0f - alpha) * color1[2] + alpha * color2[2];
|
|
|
- color3[3] = (1.0f - alpha) * color1[3] + alpha * color2[3];
|
|
|
- return color3;
|
|
|
- }
|
|
|
|
|
|
+ /**
|
|
|
+ * Will be called every ~16 milliseconds -> 60 Hz
|
|
|
+ */
|
|
|
public void onDrawFrame(GL10 arg0) {
|
|
|
+ long timeMillis = System.currentTimeMillis() - startTimeMillis;
|
|
|
+ double animTime = 0.001 * (double)timeMillis; // in seconds
|
|
|
+
|
|
|
+ // threat level state machine
|
|
|
+ if (targetThreatLevel != currentThreatLevel) {
|
|
|
+ boolean blocked = false; // block until current action is completed
|
|
|
+ if (threatLevelTransition == 0.0f) {
|
|
|
+ if (androidMesh.isActionDone()) {
|
|
|
+ switch (targetThreatLevel) {
|
|
|
+ case kThreatLevelNotMonitoring:
|
|
|
+ androidMesh.startAction("sleep", false, false);
|
|
|
+ break;
|
|
|
+ case kThreatLevelNoThreat:
|
|
|
+ androidMesh.startAction("happy", true, false);
|
|
|
+ break;
|
|
|
+ case kThreatLevelPastThreat:
|
|
|
+ androidMesh.startAction("fear", true, false);
|
|
|
+ break;
|
|
|
+ case kThreatLevelPersistentThreat:
|
|
|
+ androidMesh.startAction("panic", true, false);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else blocked = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!blocked) {
|
|
|
+ threatLevelTransition += 0.016f;
|
|
|
+ if (threatLevelTransition >= 1.0f) {
|
|
|
+ currentThreatLevel = targetThreatLevel;
|
|
|
+ threatLevelTransition = 1.0f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (nextThreatLevel != targetThreatLevel) {
|
|
|
+ targetThreatLevel = nextThreatLevel;
|
|
|
+ threatLevelTransition = 0.0f;
|
|
|
+ if (currentThreatLevel == kThreatLevelNotMonitoring)
|
|
|
+ androidMesh.startAction("sleep", false, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ androidMesh.tick(); // animate android
|
|
|
+
|
|
|
+ // OpenGL drawing
|
|
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
|
|
|
|
|
|
GLES20.glUseProgram(program);
|
|
|
int colorUniformLoc = GLES20.glGetUniformLocation(program, "color");
|
|
|
int textureUniformLoc = GLES20.glGetUniformLocation(program, "texture");
|
|
|
int mvpUniformLoc = GLES20.glGetUniformLocation(program, "mvp");
|
|
|
-
|
|
|
- //final float[] androidColor = {165.0f / 255.0f, 202.0f / 255.0f, 57.0f / 255.0f, 1.0f}; // ok
|
|
|
+
|
|
|
final float[] whiteColor = {1.0f, 1.0f, 1.0f, 1.0f};
|
|
|
+ final float[] greyColor = {0.5f, 0.5f, 0.5f, 1.0f};
|
|
|
final float[] redColor = {2.0f, 0.4f, 0.2f, 1.0f};
|
|
|
- final float[] yellowColor = {1.0f, 1.0f, 0.0f, 1.0f};
|
|
|
+ final float[] yellowColor = {1.2f * 255.0f / 166.0f, 1.2f * 255.0f / 200.0f, 0.0f, 1.0f};
|
|
|
|
|
|
- float[] color = mixColor(0.5f + 0.5f * (float)Math.sin(0.2f * beeAnimation), whiteColor, redColor);
|
|
|
- GLES20.glUniform4fv(colorUniformLoc, 1, color, 0);
|
|
|
+ float[] currentColor = whiteColor;
|
|
|
+ float blink = 0.5f + 0.5f * (float)Math.sin(12.0 * animTime);
|
|
|
+ switch (currentThreatLevel) {
|
|
|
+ case kThreatLevelNotMonitoring:
|
|
|
+ currentColor = greyColor;
|
|
|
+ break;
|
|
|
+ case kThreatLevelPastThreat:
|
|
|
+ currentColor = mixColor(blink, whiteColor, yellowColor);
|
|
|
+ break;
|
|
|
+ case kThreatLevelPersistentThreat:
|
|
|
+ currentColor = mixColor(blink, whiteColor, redColor);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (targetThreatLevel != currentThreatLevel) {
|
|
|
+ float[] targetColor = whiteColor;
|
|
|
+ switch (targetThreatLevel) {
|
|
|
+ case kThreatLevelNotMonitoring:
|
|
|
+ targetColor = greyColor;
|
|
|
+ break;
|
|
|
+ case kThreatLevelPastThreat:
|
|
|
+ targetColor = mixColor(blink, whiteColor, yellowColor);
|
|
|
+ break;
|
|
|
+ case kThreatLevelPersistentThreat:
|
|
|
+ targetColor = mixColor(blink, whiteColor, redColor);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ currentColor = mixColor(threatLevelTransition, currentColor, targetColor);
|
|
|
+ }
|
|
|
+ GLES20.glUniform4fv(colorUniformLoc, 1, currentColor, 0);
|
|
|
|
|
|
GLES20.glUniform1i(textureUniformLoc, 0);
|
|
|
|
|
|
Matrix.setIdentityM(modelview, 0);
|
|
|
- Matrix.translateM(modelview, 0, 0.0f, -0.8f, -2.0f);
|
|
|
- Matrix.rotateM(modelview, 0, -80.0f, 1.0f, 0.0f, 0.0f);
|
|
|
+ if (currentThreatLevel == kThreatLevelPersistentThreat || targetThreatLevel == kThreatLevelPersistentThreat) {
|
|
|
+ float delta = 1.0f;
|
|
|
+ if (threatLevelTransition < 0.4f) {
|
|
|
+ delta = threatLevelTransition / 0.4f;
|
|
|
+ delta = -2.0f * delta * delta * delta + 3.0f * delta * delta; // ease in/out
|
|
|
+ }
|
|
|
+ if (targetThreatLevel != kThreatLevelPersistentThreat)
|
|
|
+ delta = 1.0f - delta;
|
|
|
+ Matrix.translateM(modelview, 0, 0.0f, -0.6f - 0.2f * delta, -1.6f - 0.4f * delta); // 0.0f, -0.8f, -2.0f
|
|
|
+ Matrix.rotateM(modelview, 0, -85.0f + 5.0f * delta, 1.0f, 0.0f, 0.0f); // -80.0f
|
|
|
+
|
|
|
+ } else {
|
|
|
+ Matrix.translateM(modelview, 0, 0.0f, -0.6f, -1.6f);
|
|
|
+ Matrix.rotateM(modelview, 0, -85.0f, 1.0f, 0.0f, 0.0f);
|
|
|
+ }
|
|
|
Matrix.multiplyMM(mvp, 0, projection, 0, modelview, 0);
|
|
|
|
|
|
GLES20.glUniformMatrix4fv(mvpUniformLoc, 1, false, mvp, 0);
|
|
|
|
|
|
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, androidTexture);
|
|
|
- androidMesh.tick();
|
|
|
androidMesh.draw(program);
|
|
|
|
|
|
GLES20.glUniform4fv(colorUniformLoc, 1, whiteColor, 0);
|
|
|
|
|
|
- beeAnimation += 1.0f;
|
|
|
- float beeSize = 0.2f;
|
|
|
- Matrix.rotateM(modelview, 0, -4.0f * beeAnimation, 0.0f, 0.0f, 1.0f);
|
|
|
- Matrix.translateM(modelview, 0, 0.6f, 0.0f, 0.7f + 0.1f * (float)Math.sin(0.2f * beeAnimation));
|
|
|
- Matrix.rotateM(modelview, 0, 20.0f * (float)Math.cos(0.2f * beeAnimation), 1.0f, 0.0f, 0.0f);
|
|
|
- Matrix.scaleM(modelview, 0, beeSize, beeSize, beeSize);
|
|
|
- Matrix.multiplyMM(mvp, 0, projection, 0, modelview, 0);
|
|
|
- GLES20.glUniformMatrix4fv(mvpUniformLoc, 1, false, mvp, 0);
|
|
|
-
|
|
|
- GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, beeTexture);
|
|
|
- beeMesh.tick();
|
|
|
- beeMesh.draw(program);
|
|
|
+ if (currentThreatLevel == kThreatLevelPersistentThreat || targetThreatLevel == kThreatLevelPersistentThreat) {
|
|
|
+ // draw a bee rotating around the android
|
|
|
+
|
|
|
+ float fadeIn = threatLevelTransition;
|
|
|
+ if (targetThreatLevel != kThreatLevelPersistentThreat) fadeIn = 1.0f - fadeIn; // fade out
|
|
|
+ float beePositionZ = 2.0f * (1.0f - fadeIn) * (1.0f - fadeIn); // animate the bee going in/out
|
|
|
+
|
|
|
+ final float beeSize = 0.2f;
|
|
|
+ Matrix.rotateM(modelview, 0, (float)((-240.0 * animTime) % 360.0), 0.0f, 0.0f, 1.0f); // rotate around android
|
|
|
+ Matrix.translateM(modelview, 0, 0.6f, 0.0f, 0.7f + 0.1f * (float)Math.sin(12.0 * animTime) + beePositionZ); // go up and down
|
|
|
+ Matrix.rotateM(modelview, 0, 20.0f * (float)Math.cos(12.0 * animTime), 1.0f, 0.0f, 0.0f); // rock back and forth
|
|
|
+ Matrix.scaleM(modelview, 0, beeSize, beeSize, beeSize);
|
|
|
+ Matrix.multiplyMM(mvp, 0, projection, 0, modelview, 0);
|
|
|
+ GLES20.glUniformMatrix4fv(mvpUniformLoc, 1, false, mvp, 0);
|
|
|
+
|
|
|
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, beeTexture);
|
|
|
+ beeMesh.tick();
|
|
|
+ beeMesh.draw(program);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Informs renderer of changed surface dimensions
|
|
|
+ */
|
|
|
public void onSurfaceChanged(GL10 arg0, int w, int h) {
|
|
|
width = w;
|
|
|
height = h;
|
|
@@ -159,7 +280,18 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
GLES20.glViewport(0, 0, width, height);
|
|
|
}
|
|
|
|
|
|
- public int loadTexture(String filePath) {
|
|
|
+ // some private helper methods
|
|
|
+
|
|
|
+ private float[] mixColor(float alpha, float[] color1, float[] color2) {
|
|
|
+ float[] color3 = new float[4];
|
|
|
+ color3[0] = (1.0f - alpha) * color1[0] + alpha * color2[0];
|
|
|
+ color3[1] = (1.0f - alpha) * color1[1] + alpha * color2[1];
|
|
|
+ color3[2] = (1.0f - alpha) * color1[2] + alpha * color2[2];
|
|
|
+ color3[3] = (1.0f - alpha) * color1[3] + alpha * color2[3];
|
|
|
+ return color3;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int loadTexture(String filePath) {
|
|
|
Bitmap bitmap = null;
|
|
|
try {
|
|
|
bitmap = BitmapFactory.decodeStream(assets.open(filePath));
|
|
@@ -192,7 +324,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- public static int loadShader(int type, String source) {
|
|
|
+ private static int loadShader(int type, String source) {
|
|
|
int shader = GLES20.glCreateShader(type);
|
|
|
GLES20.glShaderSource(shader, source);
|
|
|
GLES20.glCompileShader(shader);
|
|
@@ -200,7 +332,7 @@ public class ThreatIndicatorGLRenderer implements Renderer {
|
|
|
return shader;
|
|
|
}
|
|
|
|
|
|
- public static int loadProgram(String vertexSource, String fragmentSource) {
|
|
|
+ private static int loadProgram(String vertexSource, String fragmentSource) {
|
|
|
int program = GLES20.glCreateProgram();
|
|
|
GLES20.glAttachShader(program, loadShader(GLES20.GL_VERTEX_SHADER, vertexSource));
|
|
|
GLES20.glAttachShader(program, loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource));
|