package graphics.emitter;
import graphics.mesh.EmitterMesh;
import static engine.Engine.ShaderHandler;
import static engine.Engine.elapsedAppTime;
import graphics.material.Material;
import graphics.model.Model;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import static org.lwjgl.opengl.GL11.*;
import static org.lwjgl.opengl.ARBVertexShader.GL_VERTEX_PROGRAM_POINT_SIZE_ARB;
import static org.lwjgl.opengl.ARBPointSprite.*;
import static org.lwjgl.opengl.GL20.*;
import org.lwjgl.util.vector.Vector3f;
/**
*
* @author simokr
*/
public class Emitter extends Model{
private ArrayList<Particle> particles;
private int lifeTime, updateInterval;
private float updateIntervalMultiplier;
private long lastUpdate;
private Vector3f velocity, acceleration, translation, startPositionRandomness;
private EmitterMesh emitterMesh;
private float startSize, endSize;
private float startSizeRandomness, endSizeRandomness;
private boolean isPaused;
private int maxParticles;
public Emitter(int maxParticles, int lifeTime) {
this.maxParticles = maxParticles;
particles = new ArrayList<>(maxParticles);
this.lifeTime = lifeTime;
updateInterval = lifeTime/maxParticles;
updateIntervalMultiplier = 1f;
velocity = new Vector3f();
translation = new Vector3f();
acceleration = new Vector3f();
lastUpdate = 0;
startSize = 0f;
endSize = 1f;
startSizeRandomness = 0f;
endSizeRandomness = 0f;
startPositionRandomness = new Vector3f();
isPaused = false;
emitterMesh = new EmitterMesh();
emitterMesh.create(maxParticles);
this.setMesh(emitterMesh);
shader = ShaderHandler.get("./res/shaders/billboard.vert|./res/shaders/billboard.frag");
material = new Material();
material.setTexture(0, "res/texture/smoke.png");
}
/**
*
* @param start start size (in pixels)
* @param end end size (in pixels)
*/
public void setSizes(float start, float end){
this.startSize = start;
this.endSize = end;
}
/**
* Sets the update interval multiplier. Values under 1.0f will clamp to 1.0f.
* A new particle is emitted when: current_time > previous_particle_emitting_time + (update_interval * update_interval_multiplier)
* This can be used to slow down the rate of particle emitting.
*
* @param multiplier the multiplier
*/
public void setUpdateIntervalMultiplier(float multiplier) {
this.updateIntervalMultiplier = Math.max(1.0f, multiplier);
}
public void setSizesRandomness(float startRandomness, float endRandomness){
this.startSizeRandomness = startRandomness;
this.endSizeRandomness = endRandomness;
}
public void setStartPositionRandomness(Vector3f randomness){
this.startPositionRandomness.set(randomness);
}
public void setAcceleration(Vector3f acceleration){
this.acceleration.set(acceleration);
}
public void setVelocity(Vector3f velocity){
this.velocity.set(velocity);
}
public void setTranslation(Vector3f translation){
this.translation.set(translation);
}
public void emit(int textureUnit){
if(this.particles.size() >= this.maxParticles)
return;
long currentTime = elapsedAppTime();
Random gen = new Random();
float start = this.startSize;
float end = this.endSize;
if(this.startSizeRandomness != 0.0f)
start += this.startSizeRandomness*(gen.nextFloat()-0.5f);
if(this.endSizeRandomness != 0.0f)
end += this.endSizeRandomness*(gen.nextFloat()-0.5f);
Vector3f posRand = this.getPosition();
if(this.startPositionRandomness.x != 0f && this.startPositionRandomness.y != 0f && this.startPositionRandomness.z != 0f){
posRand = new Vector3f(
(gen.nextFloat()-0.5f)*this.startPositionRandomness.x + posRand.x,
(gen.nextFloat()-0.5f)*this.startPositionRandomness.y + posRand.y,
(gen.nextFloat()-0.5f)*this.startPositionRandomness.z + posRand.z
);
}
Particle particle = new Particle(currentTime, currentTime+this.lifeTime, start, end, posRand, this.velocity);
particle.setTextureUnit(textureUnit);
this.particles.add(particle);
this.lastUpdate = currentTime;
}
public void update(){
long currentTime = elapsedAppTime();
if(!this.isPaused && currentTime > this.lastUpdate+(long)(this.updateInterval*this.updateIntervalMultiplier)){
this.emit(0);
}
Iterator<Particle> it = particles.iterator();
while(it.hasNext()){
if(it.next().isOld(currentTime)){
it.remove();
}
}
it = particles.iterator();
while(it.hasNext()){
it.next().update(this.acceleration, currentTime);
}
this.emitterMesh.update(this.particles);
}
@Override
protected void updateModelMatrix(){
if(this.modelMatrixDirty){
double rotYrad = Math.toRadians(this.rotation.y);
float sin = (float)Math.sin(rotYrad);
float cos = (float)Math.cos(rotYrad);
this.modelMatrix.setIdentity();
this.modelMatrix.translate(new Vector3f(
(-cos*this.translation.x)+sin*this.translation.z,
this.translation.y,
sin*this.translation.x+cos*this.translation.z
));
this.modelMatrixDirty = false;
}
}
@Override
protected boolean preRender(){
if(this.mesh == null || !this.mesh.isReady() || this.shader == null || !this.shader.isReady())
return false;
this.updateModelMatrix();
this.shader.setModelMatrix(this.modelMatrix);
return true;
}
@Override
public void render(){
if(!this.preRender())
return;
//glDisable(GL_CULL_FACE);
//glDisable(GL_DEPTH_TEST);
glDepthMask(false);
glEnable(GL_POINT_SPRITE_ARB);
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
glEnable(GL_BLEND);
this.shader.enable();
this.material.bindTextures();
this.mesh.render();
this.material.unbindTextures();
this.shader.disable();
glDisable(GL_BLEND);
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB);
glDisable(GL_POINT_SPRITE_ARB);
glDepthMask(true);
//glEnable(GL_CULL_FACE);
//glEnable(GL_DEPTH_TEST);
}
public void pause(){
this.isPaused = true;
}
public void resume(){
this.isPaused = false;
}
@Override
public void free(){
this.mesh.free();
}
}