package com.bitwaffle.spaceguts.entities.particles;
import java.util.ArrayList;
import java.util.Random;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.util.vector.Matrix4f;
import org.lwjgl.util.vector.Quaternion;
import org.lwjgl.util.vector.Vector3f;
import com.bitwaffle.spaceguts.entities.Entities;
import com.bitwaffle.spaceguts.entities.Entity;
import com.bitwaffle.spaceguts.graphics.render.Render3D;
import com.bitwaffle.spaceguts.graphics.shapes.Box2D;
import com.bitwaffle.spaceguts.util.QuaternionHelper;
import com.bitwaffle.spaceout.resources.Textures;
/**
* A class for shooting {@link Particle}s out
* @author TranquilMarmot
*/
public class Emitter{
/** All the particles */
private ArrayList<Particle> particles;
/** Texture to use for drawing particle */
private Textures particleTex;
/** Used to preserve modelview */
private static Matrix4f oldModelView = new Matrix4f();
/** Box, for drawing particle */
private static Box2D box = new Box2D(1.0f, 1.0f, Textures.FIRE.texture());
/** The Entity that the particles are coming from */
private Entity following;
/** Offset from the center of the Entity being followed */
private Vector3f offset;
/** How much to vary the particle generation on each axis */
private Vector3f locationVariance, velocityVariance;
/** For generating random position offsets */
private Random randy;
/** Whether or not this emitter is emitting particles */
public boolean active;
/** Controls how fast the emitter emits */
private float emitSpeed, timeSinceEmission;
/** How many particles to emit per emission */
private int particlesPerEmission;
/** How long each particle lives for */
private float particleTTLVariance;
/**
* An object for emitting particles!
* @param following Entity to emit particles from
* @param particleTex Texture to use for Entities
* @param offset Offset from center of following entity
* @param emitSpeed How often to emit particles
* @param particlesPerEmission Particles to emit per emission
*/
public Emitter(Entity following, Textures particleTex, Vector3f offset, Vector3f locationVariance, Vector3f velocityVariance, float emitSpeed, int particlesPerEmission, float particleTTLVariance){
this.following = following;
this.particleTex = particleTex;
this.offset = offset;
this.locationVariance = locationVariance;
this.velocityVariance = velocityVariance;
this.emitSpeed = emitSpeed;
this.particlesPerEmission = particlesPerEmission;
this.particleTTLVariance = particleTTLVariance;
particles = new ArrayList<Particle>();
this.active = true;
// seed with the time
randy = new Random(System.nanoTime());
}
/**
* Updates all the Emitter's particles
* @param timeStep Amount of time since last update
*/
public void update(float timeStep) {
// to avoid concurrentmodificationexception
ArrayList<Particle> deleteList = new ArrayList<Particle>();
// update particles and check for remove flag
for(Particle p : particles){
p.update(timeStep);
if(p.removeFlag)
deleteList.add(p);
}
// remove necessary particles
if(!deleteList.isEmpty()){
for(Particle p : deleteList)
this.removeParticle(p);
}
// emit if the emitter is active
if(active){
// only emit if it's been enough time
timeSinceEmission += timeStep;
if(timeSinceEmission >= emitSpeed){
// emit however many particles
for(int i = 0; i < particlesPerEmission; i++)
emitParticle();
// reset counter
timeSinceEmission = 0.0f;
}
}
}
/**
* @param p Particle to add to emitter
*/
public void addParticle(Particle p){
particles.add(p);
}
/**
* @param p Particle to add to emitter
* @param index Index to add particle to
*/
public void addParticle(Particle p, int index){
particles.add(index, p);
}
/**
* @param p Particles to add to emitter
*/
public void addParticles(ArrayList<Particle> p){
particles.addAll(p);
}
/**
* @param p Remove particle from emitter
*/
public void removeParticle(Particle p){
particles.remove(p);
}
/**
* @param index Index of particle to remove from emitter
*/
public void removeParticle(int index){
particles.remove(index);
}
/**
* @param p Particles to remove
*/
public void removeParticles(ArrayList<Particle> p){
particles.removeAll(p);
}
/**
* @return Whether or not this emitter has any particles
*/
public boolean hasParticles(){
return !this.particles.isEmpty();
}
/**
* Draws all the particles associated with this Emitter
*/
public void draw() {
// disable lighting and enable blending
Render3D.program.setUniform("Light.LightEnabled", false);
GL11.glEnable(GL11.GL_BLEND);
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
// get the revese rotation of what we're following
Quaternion revQuat = new Quaternion(0.0f, 0.0f, 0.0f, 1.0f);
this.following.rotation.negate(revQuat);
// bind texture and array handle
particleTex.texture().bind();
GL30.glBindVertexArray(box.getVAOHandle());
// draw all particles
for(Particle p : particles){
// amount to translate
float transx = this.following.location.x - p.location.x;
float transy = this.following.location.y - p.location.y;
float transz = this.following.location.z - p.location.z;
// save the modelview before we manipulate it
oldModelView.load(Render3D.modelview);{
Matrix4f.mul(Render3D.modelview, QuaternionHelper.toMatrix(revQuat), Render3D.modelview);
// translate and scale the modelview
Render3D.modelview.translate(new Vector3f(transx, transy, transz));
// billboard the particle
Matrix4f.mul(Render3D.modelview, QuaternionHelper.toMatrix(Entities.camera.rotation), Render3D.modelview);
Render3D.modelview.scale(new Vector3f(p.width, p.height, 1.0f));
Render3D.program.setUniform("ModelViewMatrix", Render3D.modelview);
// draw the particle
GL11.glDrawArrays(GL11.GL_QUADS, 0, 4);
}Render3D.modelview.load(oldModelView);
}
// re-enable lighting
Render3D.program.setUniform("Light.LightEnabled", true);
GL11.glDisable(GL11.GL_BLEND);
}
/**
* Emit a single particle from this emitter
*/
public void emitParticle(){
// offset from center of emitter
float randXOffset, randYOffset, randZOffset;
if(randy.nextBoolean())
randXOffset = randy.nextFloat() * locationVariance.x;
else
randXOffset = randy.nextFloat() * -locationVariance.x;
if(randy.nextBoolean())
randYOffset = randy.nextFloat() * locationVariance.y;
else
randYOffset = randy.nextFloat() * -locationVariance.y;
if(randy.nextBoolean())
randZOffset = randy.nextFloat() * locationVariance.z;
else
randZOffset = randy.nextFloat() * -locationVariance.z;
// place the particle at the offset for the emitter and rotate the location
Vector3f behind = QuaternionHelper.rotateVectorByQuaternion(new Vector3f(randXOffset + offset.x, randYOffset + offset.y, randZOffset + offset.z), this.following.rotation);
// add the rotated vector to the location
Vector3f loc = new Vector3f(this.following.location.x + behind.x, this.following.location.y + behind.y, this.following.location.z + behind.z);
// figure out how much this particle's going to move
float velocX, velocY, velocZ;
if(randy.nextBoolean())
velocX = randy.nextFloat() * velocityVariance.x;
else
velocX = randy.nextFloat() * -velocityVariance.x;
if(randy.nextBoolean())
velocY = randy.nextFloat() * velocityVariance.y;
else
velocY = randy.nextFloat() * -velocityVariance.y;
if(randy.nextBoolean())
velocZ = randy.nextFloat() * velocityVariance.z;
else
velocZ = randy.nextFloat() * -velocityVariance.z;
Vector3f veloc = new Vector3f(velocX, velocY, velocZ);
// rotate the velocity
Vector3f rotVeloc = QuaternionHelper.rotateVectorByQuaternion(veloc, this.following.rotation);
// get angular velocity and add it
//javax.vecmath.Vector3f angvel = new javax.vecmath.Vector3f();
//following.rigidBody.getAngularVelocity(angvel);
//rotVeloc.set(rotVeloc.x + angvel.x, rotVeloc.y + angvel.y, rotVeloc.z + angvel.z);
this.addParticle(new Particle(loc, 1.0f, 1.0f, randy.nextFloat() * particleTTLVariance, rotVeloc));
}
}