From 1dfc7c46cc83fd6c546bfdc582906c68953a15f5 Mon Sep 17 00:00:00 2001 From: AlbertSnow Date: Fri, 7 Jul 2017 14:27:06 +0800 Subject: [PATCH] 1. add particle feature --- app/src/main/AndroidManifest.xml | 15 +- .../myapplication/data/VertexArray.java | 7 +- .../objects/ParticleShooter.java | 68 +++++++++ .../myapplication/objects/ParticleSystem.java | 106 ++++++++++++++ .../particle/ParticleActivity.java | 38 +++++ .../programs/ParticleShaderProgram.java | 61 ++++++++ .../myapplication/programs/ShaderProgram.java | 3 + .../myapplication/util/Geometry.java | 137 +++++++++++++++++- .../myapplication/util/TextureHelper.java | 53 +++++++ .../myapplication/view/AirHockeyRenderer.java | 42 ++++++ .../myapplication/view/AirHockeyView.java | 40 ++++- .../myapplication/view/ParticlesRenderer.java | 112 ++++++++++++++ .../res/drawable-nodpi/particle_texture.png | Bin 0 -> 11381 bytes app/src/main/res/layout/activity_particle.xml | 9 ++ .../res/raw/particle_fragment_shader.glsl | 10 ++ .../main/res/raw/particle_vertex_shader.glsl | 22 +++ 16 files changed, 707 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleShooter.java create mode 100644 app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleSystem.java create mode 100644 app/src/main/java/com/example/albertsnow/myapplication/particle/ParticleActivity.java create mode 100644 app/src/main/java/com/example/albertsnow/myapplication/programs/ParticleShaderProgram.java create mode 100644 app/src/main/java/com/example/albertsnow/myapplication/view/ParticlesRenderer.java create mode 100644 app/src/main/res/drawable-nodpi/particle_texture.png create mode 100644 app/src/main/res/layout/activity_particle.xml create mode 100644 app/src/main/res/raw/particle_fragment_shader.glsl create mode 100644 app/src/main/res/raw/particle_vertex_shader.glsl diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 392589e..c98b02c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,10 @@ - + + @@ -14,19 +17,17 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> - - - - - + + + - \ No newline at end of file diff --git a/app/src/main/java/com/example/albertsnow/myapplication/data/VertexArray.java b/app/src/main/java/com/example/albertsnow/myapplication/data/VertexArray.java index 1e8150c..750a2a5 100644 --- a/app/src/main/java/com/example/albertsnow/myapplication/data/VertexArray.java +++ b/app/src/main/java/com/example/albertsnow/myapplication/data/VertexArray.java @@ -34,6 +34,9 @@ public void setVertexAttribPointer(int dataOffset, int attributeLocation, } - - + public void updateBuffer(float[] vertexData, int start, int count) { + floatBuffer.position(start); + floatBuffer.put(vertexData, start, count); + floatBuffer.position(0); + } } diff --git a/app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleShooter.java b/app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleShooter.java new file mode 100644 index 0000000..ec0833c --- /dev/null +++ b/app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleShooter.java @@ -0,0 +1,68 @@ +package com.example.albertsnow.myapplication.objects; + +import com.example.albertsnow.myapplication.util.Geometry; + +import java.util.Random; + +import static android.opengl.Matrix.multiplyMV; +import static android.opengl.Matrix.setRotateEulerM; + + +/** + * Created by albertsnow on 7/5/17. + */ + +public class ParticleShooter { + private final Geometry.Point position; + private final Geometry.Vector direction; + private final int color; + + private final float angleVariance; + private final float speedVariance; + + private final Random random = new Random(); + + private float[] rotationMatrix = new float[16]; + private float[] directionVector = new float[4]; + private float[] resultVector = new float[4]; + + public ParticleShooter(Geometry.Point position, Geometry.Vector direction, int color, + float angleVarianceInDegrees, float speedVariance) { + this.position = position; + this.direction = direction; + this.color = color; + + this.angleVariance = angleVarianceInDegrees; + this.speedVariance = speedVariance; + + directionVector[0] = direction.x; + directionVector[1] = direction.y; + directionVector[2] = direction.z; + } + + public void addParticles(ParticleSystem particleSystem, float currentTime, int count) { + for (int i = 0; i < count; i++) { + setRotateEulerM(rotationMatrix, 0, + (random.nextFloat() - 0.5f) * angleVariance, + (random.nextFloat() - 0.5f) * angleVariance, + (random.nextFloat() - 0.5f) * angleVariance); + + multiplyMV( + resultVector, 0, + rotationMatrix, 0, + directionVector, 0 + ); + + float speedAdjustment = 1f + random.nextFloat() * speedVariance; + + Geometry.Vector thisDirection = new Geometry.Vector( + resultVector[0] * speedAdjustment, + resultVector[1] * speedAdjustment, + resultVector[2] * speedAdjustment + ); + + particleSystem.addParticle(position, color, thisDirection, currentTime); + } + } + +} diff --git a/app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleSystem.java b/app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleSystem.java new file mode 100644 index 0000000..3219b13 --- /dev/null +++ b/app/src/main/java/com/example/albertsnow/myapplication/objects/ParticleSystem.java @@ -0,0 +1,106 @@ +package com.example.albertsnow.myapplication.objects; + +import android.graphics.Color; +import android.opengl.GLES20; +import android.util.Log; + +import com.example.albertsnow.myapplication.data.VertexArray; +import com.example.albertsnow.myapplication.programs.ParticleShaderProgram; +import com.example.albertsnow.myapplication.util.Constants; +import com.example.albertsnow.myapplication.util.Geometry; + + +/** + * Created by albertsnow on 7/5/17. + */ + +public class ParticleSystem { + private static final int POSITION_COMPONENT_COUNT = 3; + private static final int COLOR_COMPONENT_COUNT = 3; + private static final int VECTOR_COMPONENT_COUNT = 3; + private static final int PARTICLE_START_TIME_COMPONENT_COUNT = 3; + + private static final int TOTAL_COMPONENT_COUNT = POSITION_COMPONENT_COUNT + + COLOR_COMPONENT_COUNT + + VECTOR_COMPONENT_COUNT + + PARTICLE_START_TIME_COMPONENT_COUNT; + + private static final int STRIDE = TOTAL_COMPONENT_COUNT * Constants.BYTES_PER_FLOAT; + private static final String TAG = "ParticleSystemTag"; + + private final float[] particles; + private final VertexArray vertexArray; + private final int maxParticleCount; + + private int currentParticleCount; + private int nextParticle; + + public ParticleSystem(int maxParticleCount) { + particles = new float[maxParticleCount * TOTAL_COMPONENT_COUNT]; + vertexArray = new VertexArray(particles); + this.maxParticleCount = maxParticleCount; + } + + public void addParticle(Geometry.Point position, int color, Geometry.Vector director, + float particleStartTime) { + final int particleOffset = nextParticle * TOTAL_COMPONENT_COUNT; + int currentOffset = particleOffset; + nextParticle++; + + if (currentParticleCount < maxParticleCount) { + currentParticleCount++; + } + + if (nextParticle == maxParticleCount) { + nextParticle = 0; + } + + particles[currentOffset++] = position.x; + particles[currentOffset++] = position.y; + particles[currentOffset++] = position.z; + + particles[currentOffset++] = Color.red(color) / 255f; + particles[currentOffset++] = Color.green(color) / 255f; + particles[currentOffset++] = Color.blue(color) / 255f; + + particles[currentOffset++] = director.x; + particles[currentOffset++] = director.y; + particles[currentOffset++] = director.z; + particles[currentOffset++] = particleStartTime; + + vertexArray.updateBuffer(particles, particleOffset, TOTAL_COMPONENT_COUNT); + + Log.i(TAG, "add particles, size: " + nextParticle); + Log.i(TAG, "add particles, x: " + position.x); + Log.i(TAG, "add particles, y: " + position.y); + Log.i(TAG, "add particles, z: " + position.z); + } + + + public void bindData(ParticleShaderProgram particleShaderProgram) { + int dataOffset = 0; + vertexArray.setVertexAttribPointer(dataOffset, + particleShaderProgram.getPositionLocation(), + POSITION_COMPONENT_COUNT, STRIDE); + dataOffset += POSITION_COMPONENT_COUNT; + + vertexArray.setVertexAttribPointer(dataOffset, + particleShaderProgram.getColorLocation(), + COLOR_COMPONENT_COUNT, STRIDE); + dataOffset += COLOR_COMPONENT_COUNT; + + vertexArray.setVertexAttribPointer(dataOffset, + particleShaderProgram.getDirectionVectorLocation(), + VECTOR_COMPONENT_COUNT, STRIDE); + dataOffset += VECTOR_COMPONENT_COUNT; + + vertexArray.setVertexAttribPointer(dataOffset, + particleShaderProgram.getParticleStartTimeLocation(), + PARTICLE_START_TIME_COMPONENT_COUNT, STRIDE); + } + + public void draw() { + GLES20.glDrawArrays(GLES20.GL_POINTS, 0, currentParticleCount); + } + +} diff --git a/app/src/main/java/com/example/albertsnow/myapplication/particle/ParticleActivity.java b/app/src/main/java/com/example/albertsnow/myapplication/particle/ParticleActivity.java new file mode 100644 index 0000000..e1e354c --- /dev/null +++ b/app/src/main/java/com/example/albertsnow/myapplication/particle/ParticleActivity.java @@ -0,0 +1,38 @@ +package com.example.albertsnow.myapplication.particle; + +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +import com.example.albertsnow.myapplication.view.ParticlesRenderer; + +public class ParticleActivity extends AppCompatActivity { + + private GLSurfaceView surfaceView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); +// setContentView(R.layout.activity_particle); + surfaceView = new GLSurfaceView(this); + surfaceView.setEGLContextClientVersion(2); + surfaceView.setRenderer(new ParticlesRenderer()); + surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + + setContentView(surfaceView); + } + + @Override + protected void onPause() { + super.onPause(); + surfaceView.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + surfaceView.onResume(); + } + + +} diff --git a/app/src/main/java/com/example/albertsnow/myapplication/programs/ParticleShaderProgram.java b/app/src/main/java/com/example/albertsnow/myapplication/programs/ParticleShaderProgram.java new file mode 100644 index 0000000..0025923 --- /dev/null +++ b/app/src/main/java/com/example/albertsnow/myapplication/programs/ParticleShaderProgram.java @@ -0,0 +1,61 @@ +package com.example.albertsnow.myapplication.programs; + +import android.opengl.GLES20; + +import com.example.albertsnow.myapplication.MyApplication; +import com.example.albertsnow.myapplication.R; + +/** + * Created by albertsnow on 6/29/17. + */ + +public class ParticleShaderProgram extends ShaderProgram{ + + private final int uMatrixLocation; + private final int uTimeLocation; + private final int uTextureUnitLocation; + + private final int aPositionLocation; + private final int aColorLocation; + private final int aDirectionVectorLocation; + private final int aParticleStartTimeLocation; + + + public ParticleShaderProgram() { + super(MyApplication.getApplication(), R.raw.particle_vertex_shader, + R.raw.particle_fragment_shader); + uMatrixLocation = GLES20.glGetUniformLocation(program, U_MATRIX); + uTimeLocation = GLES20.glGetUniformLocation(program, U_TIME); + uTextureUnitLocation = GLES20.glGetUniformLocation(program, U_TEXTURE_UNIT); + + aPositionLocation = GLES20.glGetAttribLocation(program, A_POSITION); + aColorLocation = GLES20.glGetAttribLocation(program, A_COLOR); + aDirectionVectorLocation = GLES20.glGetAttribLocation(program, A_DIRECTION_VECTOR); + aParticleStartTimeLocation = GLES20.glGetAttribLocation(program, A_PARTICLE_START_TIME); + } + + public void setUniform(float[] matrix, float elapsedTime, int textureId) { + GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0); + GLES20.glUniform1f(uTimeLocation, elapsedTime); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); + GLES20.glUniform1i(uTextureUnitLocation, 0); + } + + public int getPositionLocation() { + return aPositionLocation; + } + + public int getColorLocation() { + return aColorLocation; + } + + public int getDirectionVectorLocation() { + return aDirectionVectorLocation; + } + + public int getParticleStartTimeLocation() { + return aParticleStartTimeLocation; + } + +} diff --git a/app/src/main/java/com/example/albertsnow/myapplication/programs/ShaderProgram.java b/app/src/main/java/com/example/albertsnow/myapplication/programs/ShaderProgram.java index 35d2ea1..459f1ef 100644 --- a/app/src/main/java/com/example/albertsnow/myapplication/programs/ShaderProgram.java +++ b/app/src/main/java/com/example/albertsnow/myapplication/programs/ShaderProgram.java @@ -15,8 +15,11 @@ public class ShaderProgram { protected static final String U_MATRIX = "u_Matrix"; protected static final String U_COLOR = "u_Color"; protected static final String U_TEXTURE_UNIT = "u_TextureUnit"; + protected static final String U_TIME = "u_Time"; // Attribute constants + protected static final String A_DIRECTION_VECTOR = "a_DirectionVector"; + protected static final String A_PARTICLE_START_TIME = "a_ParticleStartTime"; protected static final String A_POSITION = "a_Position"; protected static final String A_COLOR = "a_Color"; protected static final String A_TEXTURE_COORDINATES = "a_TextureCoordinates"; diff --git a/app/src/main/java/com/example/albertsnow/myapplication/util/Geometry.java b/app/src/main/java/com/example/albertsnow/myapplication/util/Geometry.java index 18485fd..0ce066a 100644 --- a/app/src/main/java/com/example/albertsnow/myapplication/util/Geometry.java +++ b/app/src/main/java/com/example/albertsnow/myapplication/util/Geometry.java @@ -5,31 +5,90 @@ */ public class Geometry { - public static class Point { public final float x, y, z; - public Point (float x, float y, float z) { + public Point(float x, float y, float z) { this.x = x; this.y = y; this.z = z; } - public Point translateY (float distance) { + public Point translateY(float distance) { return new Point(x, y + distance, z); } + + public Point translate(Vector vector) { + return new Point( + x + vector.x, + y + vector.y, + z + vector.z); + } + } + + public static class Vector { + public final float x, y, z; + + public Vector(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public float length() { + return (float) Math.sqrt( + x * x + + y * y + + z * z); + } + + // http://en.wikipedia.org/wiki/Cross_product + public Vector crossProduct(Vector other) { + return new Vector( + (y * other.z) - (z * other.y), + (z * other.x) - (x * other.z), + (x * other.y) - (y * other.x)); + } + + // http://en.wikipedia.org/wiki/Dot_product + public float dotProduct(Vector other) { + return x * other.x + + y * other.y + + z * other.z; + } + + public Vector scale(float f) { + return new Vector( + x * f, + y * f, + z * f); + } + + public Vector normalize() { + return scale(1f / length()); + } + } + + public static class Ray { + public final Point point; + public final Vector vector; + + public Ray(Point point, Vector vector) { + this.point = point; + this.vector = vector; + } } public static class Circle { public final Point center; public final float radius; - public Circle (Point center, float radius) { + public Circle(Point center, float radius) { this.center = center; this.radius = radius; } - public Circle scale (float scale) { + public Circle scale(float scale) { return new Circle(center, radius * scale); } } @@ -39,12 +98,78 @@ public static class Cylinder { public final float radius; public final float height; - public Cylinder (Point center, float radius, float height) { + public Cylinder(Point center, float radius, float height) { this.center = center; this.radius = radius; this.height = height; } + } + + public static class Sphere { + public final Point center; + public final float radius; + + public Sphere(Point center, float radius) { + this.center = center; + this.radius = radius; + } + } + + public static class Plane { + public final Point point; + public final Vector normal; + public Plane(Point point, Vector normal) { + this.point = point; + this.normal = normal; + } } + public static Vector vectorBetween(Point from, Point to) { + return new Vector( + to.x - from.x, + to.y - from.y, + to.z - from.z); + } + + public static boolean intersects(Sphere sphere, Ray ray) { + return distanceBetween(sphere.center, ray) < sphere.radius; + } + + // http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html + // Note that this formula treats Ray as if it extended infinitely past + // either point. + public static float distanceBetween(Point point, Ray ray) { + Vector p1ToPoint = vectorBetween(ray.point, point); + Vector p2ToPoint = vectorBetween(ray.point.translate(ray.vector), point); + + // The length of the cross product gives the area of an imaginary + // parallelogram having the two vectors as sides. A parallelogram can be + // thought of as consisting of two triangles, so this is the same as + // twice the area of the triangle defined by the two vectors. + // http://en.wikipedia.org/wiki/Cross_product#Geometric_meaning + float areaOfTriangleTimesTwo + = p1ToPoint.crossProduct(p2ToPoint).length(); + float lengthOfBase = ray.vector.length(); + + // The area of a triangle is also equal to (base * height) / 2. In + // other words, the height is equal to (area * 2) / base. The height + // of this triangle is the distance from the point to the ray. + float distanceFromPointToRay + = areaOfTriangleTimesTwo / lengthOfBase; + return distanceFromPointToRay; + } + + // http://en.wikipedia.org/wiki/Line-plane_intersection + // This also treats rays as if they were infinite. It will return a + // point full of NaNs if there is no intersection point. + public static Point intersectionPoint(Ray ray, Plane plane) { + Vector rayToPlaneVector = vectorBetween(ray.point, plane.point); + + float scaleFactor = rayToPlaneVector.dotProduct(plane.normal) + / ray.vector.dotProduct(plane.normal); + + Point intersectionPoint = ray.point.translate(ray.vector.scale(scaleFactor)); + return intersectionPoint; + } } diff --git a/app/src/main/java/com/example/albertsnow/myapplication/util/TextureHelper.java b/app/src/main/java/com/example/albertsnow/myapplication/util/TextureHelper.java index 4456df1..cc1a943 100644 --- a/app/src/main/java/com/example/albertsnow/myapplication/util/TextureHelper.java +++ b/app/src/main/java/com/example/albertsnow/myapplication/util/TextureHelper.java @@ -57,4 +57,57 @@ public static int loadTexture(int resourceId) { return textureObjectIds[0]; } + public static int loadCubeMap(Context context, int[] cubeResource) { + final int[] textureObjectIds = new int[1]; + GLES20.glGenTextures(1, textureObjectIds, 0); + + if (textureObjectIds[0] == 0) { + LoggerConfig.i(TAG, "load cube map fail"); + return 0; + } + + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + final Bitmap[] cubeBitmaps = new Bitmap[6]; + + for (int i = 0; i < 6; i++) { + cubeBitmaps[i] = + BitmapFactory.decodeResource(context.getResources(), + cubeResource[i], options); + + if (cubeBitmaps[i] == null) { + LoggerConfig.i(TAG, "Resource ID " + cubeResource[i] + + " could not be decoded."); + GLES20.glDeleteTextures(1, textureObjectIds, 0); + return 0; + } + + GLES20.glBindTexture(GLES20.GL_TEXTURE_CUBE_MAP, textureObjectIds[0]); + + GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_CUBE_MAP, GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + + GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, cubeBitmaps[0], 0); + GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, cubeBitmaps[1], 0); + + GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, cubeBitmaps[2], 0); + GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, cubeBitmaps[3], 0); + + GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, cubeBitmaps[4], 0); + GLUtils.texImage2D(GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, cubeBitmaps[5], 0); + + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + + } + + for (Bitmap bitmap : cubeBitmaps) { + bitmap.recycle(); + } + + return textureObjectIds[0]; + } + } diff --git a/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyRenderer.java b/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyRenderer.java index 46d1072..5f24013 100644 --- a/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyRenderer.java +++ b/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyRenderer.java @@ -11,6 +11,7 @@ import com.example.albertsnow.myapplication.objects.Table; import com.example.albertsnow.myapplication.programs.ColorShaderProgram; import com.example.albertsnow.myapplication.programs.TextureShaderProgram; +import com.example.albertsnow.myapplication.util.Geometry; import com.example.albertsnow.myapplication.util.MatrixHelper; import com.example.albertsnow.myapplication.util.TextureHelper; @@ -40,6 +41,10 @@ public class AirHockeyRenderer implements GLSurfaceView.Renderer { private Puck puck; + private boolean malletPress = false; + private Geometry.Point blueMalletPosition; + private final float[] invertedViewProjectionMatrix = new float[16]; + @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { @@ -53,6 +58,8 @@ public void onSurfaceCreated(GL10 gl, EGLConfig config) { colorShaderProgram = new ColorShaderProgram(MyApplication.getApplication()); texture = TextureHelper.loadTexture(R.drawable.air_hockey_surface); + + blueMalletPosition = new Geometry.Point(0f, mallet.height / 2f, 0.4f); } @@ -70,6 +77,8 @@ public void onDrawFrame(GL10 gl) { GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0); + inverteM(invertedViewProjectionMatrix, 0, viewProjectionMatrix, 0); + positionTableInScene(); textureShaderProgram.useProgram(); @@ -96,6 +105,10 @@ public void onDrawFrame(GL10 gl) { puck.draw(); } + private void inverteM(float[] invertedViewProjectionMatrix, int i, float[] viewProjectionMatrix, int i1) { + + } + private void positionTableInScene(float x, float y, float z) { Matrix.setIdentityM(modelMatrix, 0); Matrix.translateM(modelMatrix, 0, x, y, z); @@ -111,4 +124,33 @@ private void positionTableInScene() { } + public void handleTouchPress(float normalizedX, float normalizedY) { +// Ray ray = converNormallized2DPointToRay(normalizedX, normalizedY); +// Sphere malletBoundingSphere = new Sphere(new Geometry.Point( +// blueMalletPosition.x, +// blueMalletPosition,y, +// blueMalletPosition.z), +// mallet.height / 2f +// ); +// +// malletPress = Geometry.intersects(malletBoundingSphere, ray); + } + +// private Ray converNormallized2DPointToRay(float normalizedX, float normalizedY) { +// final float[] nearPointNdc = {normalizedX, normalizedY, -1, 1}; +// final float[] farPointNdc = {normalizedX, normalizedY, 1, 1}; +// +// final float[] nearPointWorld = new float[4]; +// final float[] farPointWorld = new float[4]; +// +// Matrix.multiplyMV( +// nearPointWorld, 0, invertedViewProjectionMatrix, 0, nearPointNdc, 0); +// Matrix.multiplyMV( +// farPointWorld, 0, invertedViewProjectionMatrix, 0, farPointNdc, 0); +// +// +// } + + public void handleTouchDrag(float normalizedX, float normalizedY) { + } } diff --git a/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyView.java b/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyView.java index 16c2e0f..376d6cf 100644 --- a/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyView.java +++ b/app/src/main/java/com/example/albertsnow/myapplication/view/AirHockeyView.java @@ -2,6 +2,8 @@ import android.content.Context; import android.opengl.GLSurfaceView; +import android.view.MotionEvent; +import android.view.View; /** * Created by albertsnow on 6/23/17. @@ -9,7 +11,7 @@ public class AirHockeyView extends GLSurfaceView { - private final GLSurfaceView.Renderer mRenderer; + private final AirHockeyRenderer mRenderer; public AirHockeyView(Context context) { super(context); @@ -18,6 +20,42 @@ public AirHockeyView(Context context) { setRenderer(mRenderer); setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + + setOnTouchListener(new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event != null) { + final float normalizedX = + (event.getX() / (float) v.getWidth()) * 2 - 1; + final float normalizedY = + - (event.getY() / (float) v.getHeight()) * 2 - 1; + + if (event.getAction() == MotionEvent.ACTION_DOWN) { + queueEvent(new Runnable() { + @Override + public void run() { + mRenderer.handleTouchPress( + normalizedX, normalizedY + ); + } + }); + } else if (event.getAction() == MotionEvent.ACTION_MOVE){ + queueEvent(new Runnable() { + @Override + public void run() { + mRenderer.handleTouchDrag( + normalizedX, normalizedY + ); + } + }); + } + + return true; + } else { + return false; + } + } + }); } diff --git a/app/src/main/java/com/example/albertsnow/myapplication/view/ParticlesRenderer.java b/app/src/main/java/com/example/albertsnow/myapplication/view/ParticlesRenderer.java new file mode 100644 index 0000000..eac3a85 --- /dev/null +++ b/app/src/main/java/com/example/albertsnow/myapplication/view/ParticlesRenderer.java @@ -0,0 +1,112 @@ +package com.example.albertsnow.myapplication.view; + +import android.graphics.Color; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.opengl.Matrix; + +import com.example.albertsnow.myapplication.R; +import com.example.albertsnow.myapplication.objects.ParticleShooter; +import com.example.albertsnow.myapplication.objects.ParticleSystem; +import com.example.albertsnow.myapplication.programs.ParticleShaderProgram; +import com.example.albertsnow.myapplication.util.Geometry; +import com.example.albertsnow.myapplication.util.LoggerConfig; +import com.example.albertsnow.myapplication.util.MatrixHelper; +import com.example.albertsnow.myapplication.util.TextureHelper; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +/** + * Created by albertsnow on 7/5/17. + */ + +public class ParticlesRenderer implements GLSurfaceView.Renderer { + + private static final String TAG = "ParticleRendererTag"; + private final float[] projectionMatrix = new float[16]; + private final float[] viewMatrix = new float[16]; + private final float[] viewProjectionMatrix = new float[16]; + + private ParticleShaderProgram particleShaderProgram; + private ParticleSystem particleSystem; + private ParticleShooter redParticleShooter; + private ParticleShooter greenParticleShooter; + private ParticleShooter blueParticleShooter; + + private long globalStartTime; + private float angleVarianceInDegree = 5f; + private float speedVariance = 1f; + private int texture; + + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + GLES20.glEnable(GLES20.GL_BLEND); + GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE); + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + + particleShaderProgram = new ParticleShaderProgram(); + particleSystem = new ParticleSystem(10000); + globalStartTime = System.nanoTime(); + LoggerConfig.i(TAG, "Time is: " + globalStartTime); + + + final Geometry.Vector particleDirection = new Geometry.Vector(0f, 0.5f, 0f); + + redParticleShooter = new ParticleShooter( + new Geometry.Point(-1f, 0f, 0f), + particleDirection, + Color.rgb(255, 50, 5), + angleVarianceInDegree, + speedVariance + ); + + greenParticleShooter = new ParticleShooter( + new Geometry.Point(0f, 0f, 0f), + particleDirection, + Color.rgb(25, 255, 25), + angleVarianceInDegree, + speedVariance + ); + + blueParticleShooter = new ParticleShooter( + new Geometry.Point(1f, 0f, 0f), + particleDirection, + Color.rgb(5, 50, 255), + angleVarianceInDegree, + speedVariance + ); + + texture = TextureHelper.loadTexture(R.drawable.particle_texture); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + gl.glViewport(0, 0, width, height); + + MatrixHelper.perspectiveM(projectionMatrix, 45, (float)width / (float)height, + 1f, 10f); + Matrix.setIdentityM(viewMatrix, 0); + Matrix.translateM(viewMatrix, 0, 0f, -1.5f, -5f); + Matrix.multiplyMM(viewProjectionMatrix, 0, projectionMatrix, 0, viewMatrix, 0); + } + + @Override + public void onDrawFrame(GL10 gl) { + gl.glClear(GLES20.GL_COLOR_BUFFER_BIT); + + float currentTime = (System.nanoTime() - globalStartTime) / 1000000000f; + + redParticleShooter.addParticles(particleSystem, currentTime, 5); + greenParticleShooter.addParticles(particleSystem, currentTime, 5); + blueParticleShooter.addParticles(particleSystem, currentTime, 5); + + particleShaderProgram.useProgram(); + particleShaderProgram.setUniform(viewProjectionMatrix, currentTime, texture); + particleSystem.bindData(particleShaderProgram); + particleSystem.draw(); + } + + +} diff --git a/app/src/main/res/drawable-nodpi/particle_texture.png b/app/src/main/res/drawable-nodpi/particle_texture.png new file mode 100644 index 0000000000000000000000000000000000000000..f773cdcc1a4baefc0fadb8621bd2fa69b9660c35 GIT binary patch literal 11381 zcmW++byO5@7oJ^MK)So6L!^~nP)d+)X%Ok|+(nR<7C~AXk?u|bNs$tfZV+h}Sa!et zeSgfEdFR}jIdji@pL?I@-dJr-6(W3kd;kE5UZ^VS0sshe2?Fr2Fem3KWBvbD-nuH! zfa*!cear&eUO__v0P2zmkk$|Yzyh>i>MQ+c1OkDC}R#w)B4 z4NXl=EiEl=ZEYPL9bH{rJv}{reSHH114BbYBO@bYV`CE&lUJ`^nVOoKnVFfJn_E~| zSXx?ISy@?ITie*!*xK5@e*N0c&hE{dH}>}S4h{~Ej*d=FPR`EGE-o&vuC8uwZtm{x z9v&W^o}OM_Uf$l`K0ZFazP^5be*XUc0RaJlfq_9mLBYYnAt525p`l@6VK5jB4u^+_ zhrfOMHX2efpH0ot=}Dlbf5HmzS5HpI=Z=P*_;_`Sa(Z zqN3vB;*ye*($dnhva<5>@`{Rz%F4Cd4x3{maufM;4U|?WyaByg7 z=-an%!^6WPBO{}uqu;-O9~&DRA0MBXn3$ZLoSK@No}T{kU=H}+- z=NA?h78e(P{ra`EwDkM;@8#v?KY#wLtgNiAuCA@Et*@_dY;0_9ZfUXJ=>U=jRs}7nhfpS65fp*VhOH z;^yY&_V)Jf?hc7W-rwJ&P$)DSy-{l(iiv>-9;znZ001TW?*OH~Abg0qN$;a%?4#%Y z#>d~<%MQ@Cc69T3tf{2W>L>bG{IRf*(1lka0PrlnP*l(lu-G;al=$$Q2K7TRQiP;1 zIWSLjitbsI_DB^W-Lq1{2TE%AR62aAY%?FYlz52_xO~;U-5gteJ0`GpTdq;v13Kxy zBs*dIE4!yM|D0jYUC#fS7Er4W5e4tjLYfNVE0D}*(|s`d{DL=Jw7ToiBo%E9G7e+S zL9{Tjf_RET?wZI4dh~89)*Lqdl~N?>&gkYPdqLi~kaSe?H-I-6;5GZ8+Ed46UU_kA5vFq&s()pX zG~HgEFB>-n{cdrLqs4+L^6%Y1QOFEah|Gcz~7 zJgSlKQ71XQI=&PfV8LfHc-Lk>t(7IANm-BwfO`oaKP(9Z4TE6d!f#rZeov$+{z&Km z8~}a$K4VDg62a!D6hK3mU{*2En9h+q2^aGvUY~?2(RWlDU7}WatfWg%+Gt1dJ%qK2 za%HT?8Q4Z(d{_h@;R&VY0qeLNy?0#-^WH-@zsi38Ot{g9gV!4A7?C!W=Q1-%1GK8F zA<}tApKFAoqp2dpg{y%rt>@5O*V5XmYFF*P{1|BX$?g*vfyN8aW9q01J+4GMh!85F zY8k9s`NpkIWn~jAQxjw1Qq1#hO4ycO+v*7CSuc!yLSOlfW*C{0?C&)qA_-W3OO(DP zzlKR+*3nM|R>lT=Z4ed!UL#D*@-7x1H`_dCUPC^J?2sf&RIc1;Xfw&$bNp5brbRpc z{1t%Bkxs;jd}7$$w@-nGPXB7^^p#!k`_yQ^s*0%NOFlT_=Zt8b*97^81>x(M@s10s zGA-7S%CQILHlH604371U1C*IUSCjyf8;nYLloR6pdWT*byEwgh;|R3$?*j+o6$8%; zyc4*P5EaKjT`8~y&B{n5!v+){*vaxIl`nrvk_e6=;cl+-d9sjBlMCtZ6r{z@J4M=jg~A3(L}DB+UmT`|I^tv(;s5?=;ej21}FiF2d`R2vWYlF5p>zq zYc;Je3(HimP_DPj8@&KWfs})i35S!x`uY+u>c`!Zso&eFC;NDnjDC9_(5_gnWNo!9 zD(YFhwBouWB*pvBa2n9aQkxp=ALVDQE^zF0a16Psih(z`wpZdi5t_`FD;FA}}q3lIOU`s^2 zx6i0;j5`kD?rC*3An9Hoj79j}X}{Uh4Yt*QJW!C+q%Q9*t}6i6NQg*P0heXuvsC<7 zTz(e(KM4f=+T__R=BcU({2D~`)3B`JSjAu~TsUX32Sn;d%YJ)RY znn1IIAm1&+zUItZY{4LfI%{HpSP!(#4@Y2O4GJ;g$P1eogq{z{>14-G^?Wl)E9m-r zk=H0x^N#CPoUXSOD8UmG-|B)_t-u<&L7Qg@ROP@TZ1)UCJrH4Nd|-i zDH#Y+veR#T|JD57R^)14W-kx~=R8^XVSYI9QSudSIlt;;7DN|BA*PsKAx{G{Oa>H- z)5Q`V?aTYv=D4a%QWhG9S}e-{-cL=OXTl0h&WW4u(UUgR z6|}c4K!DEPbABpm-Tu%VYFbCWu^9Zb{@1Fp7(~kiyE#ZIBR>s=`xQflo^kcU3RHKbG>_q?&9nYD9-_Cz)q&yzQbk z>6hohNqbn@%kf>iAPp)8u!7V^Dx4SuJuZk%a=RPObPsFTSfi%=r!uxnm2q2tWivSN zyojg-mOybjz$OX4{2(l&HnIvI4^GUi@I}{KZEEG-7hBN*U3Rn8T~+J>r7;ziO~zM* z+_V-v{7eY(KotO=`tXV1tqJv9cyKpVigQD}mN^W?WPigS66{ssbX^)*Yp3~yOUj$d zN(4J9MubuxsRP2h8NezUvS3wqFaAEi(dNDhTa{Zr^yIXn1BLd3ISYG+(llaMJ z_A1Ej-2+-d<1I4MrY9Y>w`WCK5^2DCa_&5%^H%ufVq&{wZz8OC$-({BYY`XMkZk1m7^{)F6Tm9)-8Ms!A<2ghXA&Q0S1-^r^gjVN& zNlS3qIqMhq##P8h_4`A0*(^%5tyfMrh?rk^%>Uf?HKWVe;oqpqB1 z{9?M-4jT@|O9J;F@ZX#L71(v0`hc5}>_Wx5O@hzbYpMLgd}=VLZuo4Zc%tLGb9Vmq z1UO}5v*;TIXB)%D^H8&+M+uDvh2W6o0x=FKBy5X>P)JCg%P@ZVeqf<^_rtuH+`c8y zdvbOB?jZEHuC37{Uke8}Ji-)?EWYVn^h;xar}B{ARI2snt6A5>mMuid-TesM+yQpE z(DuE{`3=seR`AhQ2?jG-LD)Cy`~Yeih#V|+`(qk-yc56b4!%g3(yGqm8=F#%BfP-& zleg5%BBVm3KzZPlJ7A1M;n%9+9ycghoz47C|je)}Owgc;VF)n2?DHk^+Xsd<@AsvjX z#A$!@NIG!44+d)Bz=*U{12R% z(GOwT0Cps|%lI$<;_jPYy%h4?QY*49GUkTKS>#w$wPN#}6p*k!SYSYZR8Bv;y*pB4 zi41DxgA7KWjnfzX{HP1UTzQW zM$fg!-==7mTT}3L)D`U5OvY1)g?DWx0?gi{a>0(4{DY{$MNXLoBUZIfRs7aAPO)yX z(_T>Z^07BUg=YQ>-rWr)6bphS!+%02qH&$ zph;%Yv@lb06v=HD)Xe3l+K3I17NM_q`xp$G|3sd_7pd&yaPN< zRkTF9%Fo;Mnus=dE>{!{3YXfSiQ(lgWs)pXEnQ!lqFy%V^+| zfOqK747Kk`I0rCuyQ` zw3QwS<;Rslrz9KbF1)*ADL|Pv2$!9lWLa+}@p>GsO^&A*!)CvTKB_8a*9vy@|j42 zxecD8WJ2DurFwm*58}!oemwTJD0RmBnhAuA7DJ=))^H!^@$iqRy(c-p_GlCqoi#K_ zrjFn)lMlE4#~Fd-evW!sj#kj7%tP;I8%ws{V5@N3AMwa}UEQ9XsoLypYZ?tBm2Dwa;2)Y5mHQj2dsg|X7bWO-P4Hgh zSN(Z!-R(|KbS|_i;aEJcnrFEiBVkPc{zXiQ82D=cTo;WdWTH z?C?aGuk~%jK5U@5YrCv^nc)Iupa0{uu&E8@@Ss+4gcR%kb@hgwp8&y7z^Gx|31U(LU#dDV};|H`DLYMdRa7G?7L)c;TqIN@G`W}<3Ok_>vt z9qf4xe0feiLQTpY~@lH$qRkw)=h>9cI^dyFg{Vw{9jxE=0?9)!JGgD*0cHj>2Aqm>SO*(QQGV=!N*Cqg|R|8+7BZEGnG!?Zq4 zGV@OUiIj_Y*GpE;r>bC?aQur<8I%JhL_3_TFki_%^0A<&%u3JGL#mknEb?b}4hvEk zF57JpWD*n@iOdg|9*HiH8KigC5urWV_U>gVfs&#E5Rmn4lEu5-0|xrE)UJjL{ej$O zxob4ZQ>hgh?7CWqyKj*vfZz!oJOt03agu}XS$Nt3t_O-0*|%ZIcqNzpN;(ink*j!@ z(jc!3wYN%kf$G7kB+_JD&T6C1WScdSrdjED7Y>vRfOw`QQ883uD2E%<#kR);u_i4g z1xJe|9qb0TS5$x9SuAR#%`zLTeOr5Qk7|b>FNezmPVBGoJ#9jVQ5euBdjhoQ#U}SWBRxJuK%{jq5$!WhL!>Jxv0s zJ@qI2sc?n+gT>;${?$>NjE%;(#MFcMmb?^&<3qaD)#VR4hJv?{k58_67oarZ9uvWO`T<=x}nkJI&Jnk@{ak*_tlk{QBO)UDX_>3bO}I8HRF6zL-_net8D^Uy^ezoS~G?H>E> z+#bZu?1=)^wMqD_D34oCW7Ox9DfhaO3HR+!6E~d0f^q>r?~oMr3U`=&^r{Nxpg|Tu_Eiex$okYmj!i&_3?95luJ&{3rK|7GOLETzScP-res3@`TB)tv6IUs zYaqnc&e1yTF}1Q3DZc0TKk~Ak_%UH-U~;vkQa@&eI)*bo!KN_AzeM?cn`4L4-0`C1 z&$?)`4lBNoXN`VS)cQGa7^Xj)=0KO(_9k}JdE9i5DbK>Ac)gE72P?jO482{Zpv zD(Twm>aRQxdndk2T$W7%a_FZK-p#G4dE2QNmejuqOKsZx3+&l+dwTj z+m2r28ke|&YeuN{Ebhy=KamEn#0t`7wnZu$E8ns)=hsK(eY+xVf!;|)^;1B)v%`4x1`k5|1B27pGX;b^|9rEgtKEm zw(dv25*)qY8IN{6qYRPfB9nmK%Pe3oKraL z$u~DgQu;z(pO-G8yQ*IfOr`FI-!Mvq?&fRRDn}#=qUBs3O{wF^ji{O%Mkd)x$xb9) z0*(DYOB>GdUsTXO@;FV>oRIW5P-ggbjK0GC`_FXpj(RRC1uquI&Z`r&}Uj>?*=-aax2|-7&AEYs>hG-yt%)Oh+52`6*b0Y zo8gno z+PY70;eSDmC!+A8b-VQ0%L$jA<%!6B$`!!fiNZ7j;GSB<7TuRHu?|8FLsb&!P zVo^-Gz(TSBa7wjN7MRh+ck+-IGRF3-Fum5F6(RD zn#U8UQ!2gc@FQW$4fA@h%2!c4&6Dcuk+q%on0n~zzfothkgz$g?bpAKA4zzAp8#2K z9UXjRG9DHx1*q|+(*uY?^}Z-Eqgcsb|C9BpJU!+Ysr>MJRTc+^+F;5iFM8X#j(Fi} z&^D?3Vh{%Ss}z72)dTnPYSTB&asxB29?S86PFt7dt;JhO8DqmRspD?_k8mhhTKF5{JXE8`+Lsm3dSDFhmg= z;0(4X9Qag%>**g;4lv^l{&1yprX{dlusfCss9K+&9{*0y*xl9be>uM1|CfPW{>;1n z-MRgC$I`YiN}@0f%Lwozw58#qoby??2Wr&az6;3d`n%3%bJsrEpE1v>@U%u9BbGHb z{s<5Dpx2YIRY94=Wfbm&ek+-#xy`;>H=|<$g35EWVX(W*EqdW6))nX92?@~=7)}M} z#6r%bV`P@_tV~p-)kvZr6g=DtKrnz&@*u1D z6fnoa&c#b=iPqaH;}4CF!zgxNSmNGSYh9m^AU^`)*%8h6mTk*Ff4q^S=Y4CLeVD@m zr=Sy371VsI9h0Qd+@}%?~ zZtAG;qRXeuNdc^gv&wBRjiD7+hUs0cPMP=1EYzsz1lUKg5EGkGjCp>`vv5Eosp9T@ zy@$QS=%RvX;^(MEk5s4gCqmCW;tY%}v;xdSmrSS)e_H<3O|VKLbo&^}o<&l4^F^-K zo50VVo}~czH||w0LNffiNfV+iHK^jSM{Bwy8H}sUVU@Tx8Bg{lQ3@5jjICyA6=wTv z8jonE&1pc?gqRQh@}WL(W8)IF@{APUsM;dmTsvrE<7+b+{<~FlXuwzE$R3SRR%BZ* z!9ebF{it}T4hWIJrxuN%)X-*>3_=u2lH-1vdYUw@VPl12&|-N!_(tVIOxa!nfwhKD zipv}BvzSHr%x1xw>P(d*qNe6m^=$g)I!lKL2`=x06TedQ`ub`K;D$HN!>fU%$1kA{ z;fUzGhs~P9F8o^4`abUZKhBkDwCBe|gYq;9Cf-Z^w4{L@Sg)fazz%8HM0!}=wEQI3 z;b$j(tYxEUV#~Z$Hwlwh$xU)$B|T+n6Coj)^jjnxAyaA_O8NZ zgB_D98s1Y8o}soNKT5W)r4R5E7Jv)nzD31J4Rk$|FmORo_TISTTn!HsO=`0<@qU+A zktRoWPy+8jIs7=>&TAQXiE|nZ${5;CF?_J`+|J$#X?@G!hAPHqHBBI){`S%uanN|Q zt_$qu*F6au5|NNlv&3rYNru_qJVhcsAyVq>6qv{)x~y-8{Tmt=a$?Tr z(#{bJetv{I?cvLYuKux6)Q?e1I>nxC&godM?I-bj_&P9=oj_?+5e$bFwgcNL(J(E1 zpA2Pf5bc9-cKY`AHU1OWuoV&uX|9U!&w#Oyqd(&J)>ttF+1QNm6}i_O1L)|}VHApE zia!A#?S^la^wLW2&AJi5lJ(2br}g-D^P$u;G^%}jW;CLsx$6Bx7F3UcRj`iTpZD_* zgWl|}d`K%9wD%i%2lI!hauE&Odj*7bhOV+;7-_W4$D^L;_u|nf(HSvGt|=f`IpC{> zFApUpP07qNH8X%6-mLknL1nuA+C#Jq`9hrHB_z@wA+Dt6*}E$gh)s`tarsu)~IpmAN;t*GV`34yFh^%O|6)w?LI^?qUGCBn z5Z493)mkD(8D8o8F`DhqEE9OsSCG1XZFJH%ltc8z=+U9j{3+ zpOZUMc1?aUT(-TPS6BoH_M>LWR#!#_>i|z?T5lrV)TME;eePZLXSw#;J;;%V-umZlk_x{_g(gFp#@|r4Cum6Q47rbTMII+WL##!7`Et zD+4QP;ZeaGu4x{5U7Kdr3S+e2?K`ZXz^l72rk5&LZ^{OHO)5_IT|4h0mJ#SGV2OKH zwC>eWw+x%7Uf4cHaK*yIVsigyS+&yQ8frFD&*|r3=;Dtm%0ahBD1wgmD0u)=d?s0} z&#CL@cTe-KVG(;jZ7-0h10hSAU@?yLkelmrcs(W!yiYTluJpzH`qZDWh@S_d-A(2- zyklCH6vw!PtSk3Q%}9(anoH9gCM6N)rsQ%kA38_Fa`BU>u?KbNqSUujJhD9v?3Y_3 zf83F6jr7<<);MN3EZb6zBPj8hC~h)Q_Wqx{F>WoG*~^wTj3bA zvcP$AbGlvhE^9Pp?9cSvQ^|{DYCn|9Br;fA9%m6GYPxGz_~oa6Mt;{HmFJ762j41E z|5khf%hL39SWW|aIOxX^Z9U#)=42i4==9{App55OUgcQQU8Bn_ zH!1IJGMHc7S`t4zH?K&O&no7>#>pS7&-0jyz4J#IQN26NvSKMWg>+}r#TH)>W-U6C zMZ2eRg!C8crRtTgfODqn&E?s*jfhG-5okGbQD-^Q9?oXqj&E2{zU_FNL$b>je?N`V zSV6|(TjlDTb?#MvNUUr6IWs*+*O6rAa(_SHI~=_G-LJ}$YR}Exx*+y3RF@K+PX&x> zFUO6nHBR|EsZA$;>PEG!O4UFU6w$-laD9+N4910*yT98Y ziX~ea3#<3J>|y?2-oOlPGLzDp>Js@>;U^QU-#0LM2#Z2&NQlmf-b5mxI!0ZvynX)4 zS@h(7^xgBe3Y{@?7mdqlQMy0XXl)!!g0M2_736sQ<+JS6cTW>zn>QF<(!*%DblP4K zG*#NlN)qfH>I>mb26$6oPa$i`VV;r(OcdFiVLBCUR75?Di`Uy>t}py-%CBwIpA&I9 z>L@tCqfnqBGEB?!G6U26#)u$Wa~I=B%UHHg|H{o@5I`4EXI^#|PLVYY;&|}IBa#3# z=!E6ua^VM92lV=Z0-yG*P_6M6Q~ev~(O=wMtHO87UX?=ZPo^j4^;M|kQl16ff2c=n z;uqAzi6)Cu3}-}MtsOC>v^Si+s!=bvw;L#QjV-hwKEIr+Z4q9h5RO6DUP4J&R4L)^ zA4V$SLTl8tX>~O_VeaZ{=U;6hhwP!b@_=?a_yIr*U?vs-gnJ3c8vaXLY(JnFENO6m z68Bp3O%E{>t93{QedjD9bN6#ZCX7Uk=npJ`$o*<#nVp?Z6qb|6mamuXh}E zMY9|CvnZ5F;Bszju+CqHCkF##)5LIjo}$mTZ~jp=R~Ov6X{oPypQq$RS{WS%(&MLk zBieo-NL;W0v@0c^&nhV$o1rEt-uBLcsl<(nyPQW|H0Ly`zS<$QwWX_~IhC>;n7$}0 zY)8ukvGM_Wpq}K&x3@CREo+0w%4q2CK0z4M3VqA+=rb(?oUS<0JzUXIEP0SaoVCbS zo;YqKNbk;$Y=4Lbb_Qlb&;B*1*>H0$eoPXLCh|a%(S6nfiB;$+E3Nhi-W)I)XoW4) z-7M**Q2pJssx0Ao0A}SE0&F8Ng_l@nf)QqN_L zARHtQAw{Y4(5r$RaE;|z)AeVcm~S!k+%K+5c(O6GOvatni|eLQPs@y6Mh@0$1idZt1*KI!FJ{EV?wB$ssGP1WaXPF^Vsxs{ z*jza>pCFjQgY-OqgbYIr@FfD@aTr{hyp3_Qn5$FCq9_FovfbeTRO`^>P*b4SU~`m zq9=n@C`cc`e)fh~j*L#g`&zuCqdPqM=1T_cmlybN0f4-hcAJC>UI9;e=bVvQpBJjF z%&wU~z0&$)SXo^&^P{bDK1+0osysxV_4Ouox}oUxONlEm&6-JIRYPn&;t>KdWW1Ba z59EuGWh}H13ADc>@DLSr2+wF_eaepqxBUdw#l*~;NfUB!jn1ceeFzvd?Z~a1McD z<8Nngmmd*shC@q%d%{GMnV_p!0abX9;ctf+%!Jwfjt)jQw8G{3DI(-ez@J^+`?mL& zJiDYq;=Yd_*Lz%40PE}5n;&QWR&j4&l6#*Yi=+iE)#NEMo5Jy@HFn9h8(&1n)=G}* zQg?+tu|Tjx97=`HRcV!(aly0L`*9v3KQ3=sFO0E+wlg*4=lEID`-e2uLdGvxfeo~` z8QiIVtK#LdE?4Z|$fU!|K?&NhAe37b7LWWCU-=F?^@L9~1$H~@nF^@Ni>E}Tm@cEJ z*JC4u817r~0f};M4ZEGE<=o4&yVge`Sb=8YK_app{STj&FcP?4NB0gBalN!ASL1Ra zEBLoAj`Rdo#x~}-MXEOQZl*)Et;o}u`y%qT?pIH7oalw$PH-SuHpwX#@y@^51A_`m t>eE9o#=@$S1 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_particle.xml b/app/src/main/res/layout/activity_particle.xml new file mode 100644 index 0000000..0dd2e81 --- /dev/null +++ b/app/src/main/res/layout/activity_particle.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/raw/particle_fragment_shader.glsl b/app/src/main/res/raw/particle_fragment_shader.glsl new file mode 100644 index 0000000..547d96d --- /dev/null +++ b/app/src/main/res/raw/particle_fragment_shader.glsl @@ -0,0 +1,10 @@ +precision mediump float; + +varying vec3 v_Color; +varying float v_ElapsedTime; +uniform sampler2D u_TextureUnit; + +void main() { + gl_FragColor = vec4(v_Color / v_ElapsedTime, 1.0) + * texture2D(u_TextureUnit, gl_PointCoord); +} diff --git a/app/src/main/res/raw/particle_vertex_shader.glsl b/app/src/main/res/raw/particle_vertex_shader.glsl new file mode 100644 index 0000000..c240ee3 --- /dev/null +++ b/app/src/main/res/raw/particle_vertex_shader.glsl @@ -0,0 +1,22 @@ +uniform mat4 u_Matrix; +uniform float u_Time; + +attribute vec3 a_Position; +attribute vec3 a_Color; +attribute vec3 a_DirectionVector; +attribute float a_ParticleStartTime; + +varying vec3 v_Color; +varying float v_ElapsedTime; + +void main() { + v_Color = a_Color; + + v_ElapsedTime = u_Time - a_ParticleStartTime; + float gravityFactor = v_ElapsedTime * v_ElapsedTime / 8.0; + vec3 currentPosition = a_Position + (a_DirectionVector * v_ElapsedTime); + currentPosition.y -= gravityFactor; + + gl_Position = u_Matrix * vec4(currentPosition, 1.0); + gl_PointSize = 25.0; +}