public class

ParticleSystem

extends Entity
package org.anddev.andengine.entity.particle;

import static org.anddev.andengine.util.MathUtils.RANDOM;
import static org.anddev.andengine.util.constants.Constants.VERTEX_INDEX_X;
import static org.anddev.andengine.util.constants.Constants.VERTEX_INDEX_Y;

import java.util.ArrayList;

import javax.microedition.khronos.opengles.GL10;

import org.anddev.andengine.engine.camera.Camera;
import org.anddev.andengine.entity.Entity;
import org.anddev.andengine.entity.particle.emitter.IParticleEmitter;
import org.anddev.andengine.entity.particle.emitter.RectangleParticleEmitter;
import org.anddev.andengine.entity.particle.initializer.IParticleInitializer;
import org.anddev.andengine.entity.particle.modifier.IParticleModifier;
import org.anddev.andengine.opengl.texture.region.TextureRegion;
import org.anddev.andengine.opengl.vertex.RectangleVertexBuffer;

import android.util.FloatMath;

/**
 * TODO Check if SpriteBatch can be used here to improve performance. 
 * 
 * (c) 2010 Nicolas Gramlich 
 * (c) 2011 Zynga Inc.
 * 
 * @author Nicolas Gramlich
 * @since 19:42:27 - 14.03.2010
 */
public class ParticleSystem extends Entity {
	// ===========================================================
	// Constants
	// ===========================================================

	private static final int BLENDFUNCTION_SOURCE_DEFAULT = GL10.GL_ONE;
	private static final int BLENDFUNCTION_DESTINATION_DEFAULT = GL10.GL_ONE_MINUS_SRC_ALPHA;

	private final float[] POSITION_OFFSET = new float[2];

	// ===========================================================
	// Fields
	// ===========================================================

	private final IParticleEmitter mParticleEmitter;

	private final Particle[] mParticles;

	private int mSourceBlendFunction = BLENDFUNCTION_SOURCE_DEFAULT;
	private int mDestinationBlendFunction = BLENDFUNCTION_DESTINATION_DEFAULT;

	private final ArrayList<IParticleInitializer> mParticleInitializers = new ArrayList<IParticleInitializer>();
	private final ArrayList<IParticleModifier> mParticleModifiers = new ArrayList<IParticleModifier>();

	private final float mRateMinimum;
	private final float mRateMaximum;

	private final TextureRegion mTextureRegion;

	private boolean mParticlesSpawnEnabled = true;

	private final int mParticlesMaximum;
	private int mParticlesAlive;
	private float mParticlesDueToSpawn;

	private int mParticleModifierCount;
	private int mParticleInitializerCount;

	private RectangleVertexBuffer mSharedParticleVertexBuffer;

	// ===========================================================
	// Constructors
	// ===========================================================

	/**
	 * Creates a ParticleSystem with a {@link RectangleParticleEmitter}.
	 * @deprecated Instead use {@link ParticleSystem#ParticleSystem(IParticleEmitter, float, float, int, TextureRegion)}.
	 */
	@Deprecated
	public ParticleSystem(final float pX, final float pY, final float pWidth, final float pHeight, final float pRateMinimum, final float pRateMaximum, final int pParticlesMaximum, final TextureRegion pTextureRegion) {
		this(new RectangleParticleEmitter(pX + pWidth * 0.5f, pY + pHeight * 0.5f, pWidth, pHeight), pRateMinimum, pRateMaximum, pParticlesMaximum, pTextureRegion);
	}

	public ParticleSystem(final IParticleEmitter pParticleEmitter, final float pRateMinimum, final float pRateMaximum, final int pParticlesMaximum, final TextureRegion pTextureRegion) {
		super(0, 0);

		this.mParticleEmitter = pParticleEmitter;
		this.mParticles = new Particle[pParticlesMaximum];
		this.mRateMinimum = pRateMinimum;
		this.mRateMaximum = pRateMaximum;
		this.mParticlesMaximum = pParticlesMaximum;
		this.mTextureRegion = pTextureRegion;

		this.registerUpdateHandler(this.mParticleEmitter);
	}

	// ===========================================================
	// Getter & Setter
	// ===========================================================

	public boolean isParticlesSpawnEnabled() {
		return this.mParticlesSpawnEnabled;
	}

	public void setParticlesSpawnEnabled(final boolean pParticlesSpawnEnabled) {
		this.mParticlesSpawnEnabled = pParticlesSpawnEnabled;
	}

	public void setBlendFunction(final int pSourceBlendFunction, final int pDestinationBlendFunction) {
		this.mSourceBlendFunction = pSourceBlendFunction;
		this.mDestinationBlendFunction = pDestinationBlendFunction;
	}

	public IParticleEmitter getParticleEmitter() {
		return this.mParticleEmitter;
	}

	// ===========================================================
	// Methods for/from SuperClass/Interfaces
	// ===========================================================

	@Override
	public void reset() {
		super.reset();

		this.mParticlesDueToSpawn = 0;
		this.mParticlesAlive = 0;
	}

	@Override
	protected void onManagedDraw(final GL10 pGL, final Camera pCamera) {
		final Particle[] particles = this.mParticles;
		for(int i = this.mParticlesAlive - 1; i >= 0; i--) {
			particles[i].onDraw(pGL, pCamera);
		}
	}

	@Override
	protected void onManagedUpdate(final float pSecondsElapsed) {
		super.onManagedUpdate(pSecondsElapsed);
		
		if(this.mParticlesSpawnEnabled) {
			this.spawnParticles(pSecondsElapsed);
		}

		final Particle[] particles = this.mParticles;

		final ArrayList<IParticleModifier> particleModifiers = this.mParticleModifiers;
		final int particleModifierCountMinusOne = this.mParticleModifierCount - 1;

		for(int i = this.mParticlesAlive - 1; i >= 0; i--) {
			final Particle particle = particles[i];

			/* Apply all particleModifiers */
			for(int j = particleModifierCountMinusOne; j >= 0; j--) {
				particleModifiers.get(j).onUpdateParticle(particle);
			}

			particle.onUpdate(pSecondsElapsed);
			if(particle.mDead){
				this.mParticlesAlive--;
				final int particlesAlive = this.mParticlesAlive;
				particles[i] = particles[particlesAlive];
				particles[particlesAlive] = particle;
			}
		}
	}

	// ===========================================================
	// Methods
	// ===========================================================

	public void addParticleModifier(final IParticleModifier pParticleModifier) {
		this.mParticleModifiers.add(pParticleModifier);
		this.mParticleModifierCount++;
	}

	public void removeParticleModifier(final IParticleModifier pParticleModifier) {
		this.mParticleModifierCount--;
		this.mParticleModifiers.remove(pParticleModifier);
	}

	public void addParticleInitializer(final IParticleInitializer pParticleInitializer) {
		this.mParticleInitializers.add(pParticleInitializer);
		this.mParticleInitializerCount++;
	}

	public void removeParticleInitializer(final IParticleInitializer pParticleInitializer) {
		this.mParticleInitializerCount--;
		this.mParticleInitializers.remove(pParticleInitializer);
	}

	private void spawnParticles(final float pSecondsElapsed) {
		final float currentRate = this.determineCurrentRate();
		final float newParticlesThisFrame = currentRate * pSecondsElapsed;

		this.mParticlesDueToSpawn += newParticlesThisFrame;

		final int particlesToSpawnThisFrame = Math.min(this.mParticlesMaximum - this.mParticlesAlive, (int)FloatMath.floor(this.mParticlesDueToSpawn));
		this.mParticlesDueToSpawn -= particlesToSpawnThisFrame;

		for(int i = 0; i < particlesToSpawnThisFrame; i++){
			this.spawnParticle();
		}
	}

	private void spawnParticle() {
		final Particle[] particles = this.mParticles;

		final int particlesAlive = this.mParticlesAlive;
		if(particlesAlive < this.mParticlesMaximum){
			Particle particle = particles[particlesAlive];

			/* New particle needs to be created. */
			this.mParticleEmitter.getPositionOffset(this.POSITION_OFFSET);

			final float x = this.POSITION_OFFSET[VERTEX_INDEX_X];
			final float y = this.POSITION_OFFSET[VERTEX_INDEX_Y];

			if(particle != null) {
				particle.reset();
				particle.setPosition(x, y);
			} else {
				if(particlesAlive == 0) {
					/* This is the very first particle. */
					particle = new Particle(x, y, this.mTextureRegion);
					this.mSharedParticleVertexBuffer = particle.getVertexBuffer();
				} else {
					particle = new Particle(x, y, this.mTextureRegion, this.mSharedParticleVertexBuffer);
				}
				particles[particlesAlive] = particle;
			}
			particle.setBlendFunction(this.mSourceBlendFunction, this.mDestinationBlendFunction);

			/* Apply particle initializers. */
			{
				final ArrayList<IParticleInitializer> particleInitializers = this.mParticleInitializers;
				for(int i = this.mParticleInitializerCount - 1; i >= 0; i--) {
					particleInitializers.get(i).onInitializeParticle(particle);
				}

				final ArrayList<IParticleModifier> particleModifiers = this.mParticleModifiers;
				for(int i = this.mParticleModifierCount - 1; i >= 0; i--) {
					particleModifiers.get(i).onInitializeParticle(particle);
				}
			}

			this.mParticlesAlive++;
		}
	}

	private float determineCurrentRate() {
		if(this.mRateMinimum == this.mRateMaximum){
			return this.mRateMinimum;
		} else {
			return (RANDOM.nextFloat() * (this.mRateMaximum - this.mRateMinimum)) + this.mRateMinimum;
		}
	}

	// ===========================================================
	// Inner and Anonymous Classes
	// ===========================================================
}