Tags

, , , ,

Computer graphics is really an interesting domain to work in. There are numerous topics to study in computer graphics but in this article I am gonna discuss about a simple particle system. Which I am gonna demonstrate on the Android canvas. Particle systems are used to simulate the fire, smoke, fog, explosions etc. This demonstration would simulate the sparkle effect on touching the screen of the Android device. As you can see in the video below:

Let’s first discuss about few basics of the simple particle systems. In a simple particle system every particle has a life span. Particles take birth, lives their life and dies. In a particle system there is an emitter of the particles which generates the particles. In our case the emitter is the point where the user touches the screen and the particles have the life span till they are visible on the screen of the device. As the particles go off the screen the particles get removed from the drawing list of the particles.

In the demo the class Particle represents a single particle. The init() method initializes the particle generation point (initX, initY) and its distance form that point. When the Particle object is created the particle is initialized with init() method, set the random color out of (0,1,2), direction out of NO_OF_DIRECTION directions, directionCosine and directionSine. The directionCosine and directionSine are just to improve the performance of the animation so that these values would not get calculated again and again on each drawing on the canvas. And move() is just to increase the distFromOrigin for the movement of the particle in animation.

package com.sparkle;

import java.util.Random;

public class Particle {
	public int distFromOrigin = 0;
	private double direction;
	private double directionCosine;
	private double directionSine;
	public int color;
	public int x;
	public int y;
	private int initX;
	private int initY;

	public Particle(int x, int y) {
		init(x, y);
		this.direction = 2*Math.PI * new Random().nextInt(NO_OF_DIRECTION)/NO_OF_DIRECTION;
		this.directionCosine = Math.cos(direction);
		this.directionSine = Math.sin(direction);
		this.color = new Random().nextInt(3);
	}

	public void init(int x, int y) {
		distFromOrigin = 0;
		this.initX = this.x = x;
		this.initY = this.y = y;
	}

	public synchronized void move(){
		distFromOrigin +=2;
		x = (int) (initX+distFromOrigin*directionCosine);
		y = (int) (initY+distFromOrigin*directionSine);
	}
	private final static int NO_OF_DIRECTION = 400; 

}

ParticleDrawingThread is a thread, which is responsible for drawing on the canvas of the view. Basically there are two lists of particle one is ‘mParticleList’ which has to be drawn on the canvas and another ‘mRecycleList’ which is used for holding all the particle which get off the screen and ready for the recycling. The ParticleDrawingThread removes the particles from the ‘mParticleList’ if the particles get off the screen add them to the ‘mRecycleList’. With this recycling we can reduce allocation and deallocation of the particle object which improves the performance.

package com.sparkle;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.view.SurfaceHolder;

class ParticleDrawingThread extends Thread {

	private boolean mRun = true;

	private SurfaceHolder mSurfaceHolder;

	private ArrayList mParticleList =new ArrayList();
	private ArrayList mRecycleList =new ArrayList();

	private int mCanvasWidth;
	private int mCanvasHeight;
	private Paint mPaint;
	private Bitmap mImage[] =new Bitmap[3];

	public ParticleDrawingThread(SurfaceHolder mSurfaceHolder, Context mContext) {
		this.mSurfaceHolder = mSurfaceHolder;
		this.mPaint = new Paint();
		mPaint.setColor(Color.WHITE);
		mImage[0] =((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.yellow_spark)).getBitmap();
		mImage[1] =((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.blue_spark)).getBitmap();
		mImage[2] =((BitmapDrawable)mContext.getResources().getDrawable(R.drawable.red_spark)).getBitmap();

	}

	@Override
	public void run() {
		while (mRun) {
			Canvas c = null;
			try {
				c = mSurfaceHolder.lockCanvas(null);
				synchronized (mSurfaceHolder) {
					doDraw(c);
				}
			} finally {
				if (c != null) {
					mSurfaceHolder.unlockCanvasAndPost(c);
				}
			}
		}
	}

	private void doDraw(Canvas c) {
		c.drawRect(0, 0, mCanvasWidth, mCanvasHeight, mPaint);
		synchronized (mParticleList) {
			for (int i = 0; i < mParticleList.size(); i++) {
				Particle p = mParticleList.get(i);
				p.move();
				c.drawBitmap(mImage[p.color], p.x-10, p.y-10, mPaint);
				if (p.x < 0 || p.x > mCanvasWidth || p.y < 0 || p.y > mCanvasHeight) {
					mRecycleList.add(mParticleList.remove(i));
					i--;
				}
			}
		}
	}

	public void stopDrawing() {
		this.mRun = false;
	}

	public ArrayList getParticleList() {
		return mParticleList;
	}

	public ArrayList getRecycleList() {
		return mRecycleList;
	}

	public void setSurfaceSize(int width, int height) {
		mCanvasWidth = width;
		mCanvasHeight = height;
	}

}

In ParticalView main thing to concentrate about is particle generation. onTouchEvent() method is responsible for adding particle to the ‘mParticleList’. If the particle is available in the ‘mRecycleList’ it would get added from there otherwise create new particle object and add to the ‘mParticleList’.

package com.sparkle;

import java.util.ArrayList;

import android.content.Context;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class ParticalView extends SurfaceView implements SurfaceHolder.Callback {

	private ParticleDrawingThread mDrawingThread;

	private ArrayList mParticleList;
	private ArrayList mRecycleList;

	private Context mContext;

	public ParticalView(Context context) {
		super(context);
		SurfaceHolder holder = getHolder();
		holder.addCallback(this);
		this.mContext = context;

	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		mDrawingThread.setSurfaceSize(width, height);
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		mDrawingThread = new ParticleDrawingThread(holder, mContext);
		mParticleList = mDrawingThread.getParticleList();
		mRecycleList = mDrawingThread.getRecycleList();
		mDrawingThread.start();
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		boolean retry = true;
		mDrawingThread.stopDrawing();
		while (retry) {
			try {
				mDrawingThread.join();
				retry = false;
			} catch (InterruptedException e) {
			}
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		Particle p;
		int recycleCount = 0;

		if(mRecycleList.size()>1)
			recycleCount = 2;
		else
			recycleCount =mRecycleList.size();

		for (int i = 0; i < recycleCount; i++) {
			p = mRecycleList.remove(0);
			p.init((int) event.getX(), (int) event.getY());
			mParticleList.add(p);
		}

		for (int i = 0; i < 2-recycleCount; i++)
			mParticleList.add(new Particle((int)event.getX(), (int)event.getY()));

		return super.onTouchEvent(event);
	}

}

Checkout code: https://github.com/manoj-chauhan/Sparkles

Advertisements