/*******************************************************************************
* Copyright (c) 2013 Philip Collin.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*
* Contributors:
* Philip Collin - initial API and implementation
******************************************************************************/
package com.lyeeedar.Graphics.ParticleEffects;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.files.FileHandle;
import com.badlogic.gdx.graphics.Camera;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Mesh;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.VertexAttribute;
import com.badlogic.gdx.graphics.VertexAttributes.Usage;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.Pools;
import com.badlogic.gdx.utils.ObjectMap.Entry;
import com.badlogic.gdx.utils.OrderedMap;
import com.lyeeedar.Roguelike3D.Graphics.Lights.LightManager;
import com.lyeeedar.Roguelike3D.Graphics.Lights.PointLight;
import com.lyeeedar.Utils.Bag;
import com.lyeeedar.Utils.FileUtils;
public class ParticleEmitter implements Serializable {
private transient static final int VERTEX_SIZE = 9;
private static final long serialVersionUID = 6308492057144008114L;
public enum ParticleAttribute {
SPRITE,
SIZE,
COLOUR,
VELOCITY
}
public final String UID;
public String name;
// ----- Particle Parameters ----- //
private TimelineValue[] sprite;
private TimelineValue[] size;
private TimelineValue[] colour;
private TimelineValue[] velocity;
// ----- End Particle Parameters ----- //
// ----- Emitter parameters ----- //
public int maxParticles;
public float particleLifetime;
public float particleLifetimeVar;
public float emissionTime;
public float ex, ey, ez;
public int emissionType;
public int blendFuncSRC;
public int blendFuncDST;
public String atlasName;
// ----- End Emitter parameters ----- //
// ----- Light ----- //
private float lightAttenuation;
private float lightPower;
private boolean isLightStatic;
private Color lightColour;
private boolean lightFlicker;
private float lightx, lighty, lightz;
// ----- End Light ----- //
// ----- Transient Variables ----- //
public transient float distance = 0;
private transient static ShaderProgram shader;
private transient static String currentAtlas;
private transient static int currentBlendSRC, currentBlendDST;
public transient TextureAtlas atlas;
public transient Texture atlasTexture;
private transient float[][] topLeftTexCoords;
private transient float[][] topRightTexCoords;
private transient float[][] botLeftTexCoords;
private transient float[][] botRightTexCoords;
private transient Bag<Particle> active;
private transient Bag<Particle> inactive;
private transient Vector3 quad;
private transient float[] vertices;
private transient Mesh mesh;
private transient Random ran;
private transient Matrix4 tmpMat;
private transient Matrix4 tmpRot;
private transient Vector3 pos;
private transient int signx;
private transient int signy;
private transient int signz;
private transient int v;
private transient float emissionCD;
private transient PointLight light;
private transient int i;
private transient int i2;
private transient int arrayLen;
// ----- End Transient Variables ----- //
// ----- Non-Essential Variables ----- //
private String lightUID;
private float x, y, z;
private float radius;
// ----- End Non-Essential Variables ----- //
public ParticleEmitter(float particleLifetime, float particleLifetimeVar, float emissionTime,
float ex, float ey, float ez,
int emissionType,
int blendFuncSRC, int blendFuncDST,
String atlasName,
String name)
{
this.UID = this.toString()+this.hashCode()+System.currentTimeMillis()+System.nanoTime();
this.name = name;
this.particleLifetime = particleLifetime;
this.particleLifetimeVar = particleLifetimeVar;
this.emissionTime = emissionTime;
this.ex = ex;
this.ey = ey;
this.ez = ez;
this.emissionType = emissionType;
this.blendFuncSRC = blendFuncSRC;
this.blendFuncDST = blendFuncDST;
this.atlasName = atlasName;
}
public int getActiveParticles() {
return active.size;
}
public float getRadius()
{
return radius;
}
public Vector3 getPosition()
{
return pos.set(x, y, z);
}
public void setPosition(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
if (light != null) light.position.set(x+lightx, y+lighty, z+lightz);
}
public void setTimeline(TimelineValue[] sprite, TimelineValue[] size, TimelineValue[] colour, TimelineValue[] velocity)
{
this.sprite = sprite;
this.size = size;
this.colour = colour;
this.velocity = velocity;
}
public TimelineValue[] getSpriteTimeline()
{
return sprite;
}
/**
* Set the sprite number timeline. <p>
* Each point in the timeline is specified by a float array. <br> array[0] is the time in the timeline, array[1] is the sprite number (casted to an int)
* Optionally setting interpolated to true will linearly interpolate between each value in the timeline
* @param values
*/
public void setSpriteTimeline(boolean interpolated, float[]... values)
{
this.sprite = new TimelineValue[values.length];
for (int i = 0; i < values.length; i++)
{
this.sprite[i] = new TimelineValue(values[i][0], values[i][1]);
}
for (int i = 0; i < values.length-1; i++)
{
this.sprite[i].setInterpolated(true, this.sprite[i+1]);
}
}
public void setSpriteTimeline(List<TimelineValue> values)
{
TimelineValue[] array = new TimelineValue[values.size()];
sprite = values.toArray(array);
}
public TimelineValue[] getSizeTimeline()
{
return size;
}
/**
* Set the size timeline. <p>
* Each point in the timeline is specified by a float array. <br> array[0] is the time in the timeline, array[1] is the width, array[2] is the height
* Optionally setting interpolated to true will linearly interpolate between each value in the timeline
* @param interpolated
* @param values
*/
public void setSizeTimeline(boolean interpolated, float[]... values)
{
this.size = new TimelineValue[values.length];
for (int i = 0; i < values.length; i++)
{
this.size[i] = new TimelineValue(values[i][0], values[i][1], values[i][2]);
}
for (int i = 0; i < values.length-1; i++)
{
this.size[i].setInterpolated(true, this.size[i+1]);
}
}
public void setSizeTimeline(List<TimelineValue> values)
{
TimelineValue[] array = new TimelineValue[values.size()];
size = values.toArray(array);
}
public TimelineValue[] getColourTimeline()
{
return colour;
}
/**
* Set the colour timeline. <p>
* Each point in the timeline is specified by a float array. <br> array[0] is the time in the timeline, array[1] is red, array[2] is green, array[3] is blue and array[4] is alpha
* Optionally setting interpolated to true will linearly interpolate between each value in the timeline
* @param interpolated
* @param values
*/
public void setColourTimeline(boolean interpolated, float[]... values)
{
this.colour = new TimelineValue[values.length];
for (int i = 0; i < values.length; i++)
{
this.colour[i] = new TimelineValue(values[i][0], values[i][1], values[i][2], values[i][3], values[i][4]);
}
for (int i = 0; i < values.length-1; i++)
{
this.colour[i].setInterpolated(true, this.colour[i+1]);
}
}
public void setColourTimeline(List<TimelineValue> values)
{
TimelineValue[] array = new TimelineValue[values.size()];
colour = values.toArray(array);
}
public TimelineValue[] getVelocityTimeline()
{
return velocity;
}
/**
* Set the velocity timeline. <p>
* Each point in the timeline is specified by a float array. <br> array[0] is the time in the timeline, array[1] is the x velocity, array[2] is the y velocity and array[3] is the z velocity
* Optionally setting interpolated to true will linearly interpolate between each value in the timeline
* @param interpolated
* @param values
*/
public void setVelocityTimeline(boolean interpolated, float[]... values)
{
this.velocity = new TimelineValue[values.length];
for (int i = 0; i < values.length; i++)
{
this.velocity[i] = new TimelineValue(values[i][0], values[i][1], values[i][2], values[i][3]);
}
for (int i = 0; i < values.length-1; i++)
{
this.velocity[i].setInterpolated(true, this.velocity[i+1]);
}
}
public void setVelocityTimeline(List<TimelineValue> values)
{
TimelineValue[] array = new TimelineValue[values.size()];
velocity = values.toArray(array);
}
/**
* Method to create a basic particle emitter. <p>
* The particles in this emitter will have a constant width and height,
* a constant velocity (vx, vy, vz)
* and will interpolate its colour from the start colour to the end over the particles lifetime.
* The image used will be the sprite in the atlas designated as 'sprite0'.
* @param width
* @param height
* @param start
* @param end
* @param vx
* @param vy
* @param vz
*/
public void createBasicEmitter(float width, float height, Color start, Color end, float vx, float vy, float vz)
{
this.sprite = new TimelineValue[]{new TimelineValue(0, 0)};
this.size = new TimelineValue[]{new TimelineValue(0, width, height)};
this.colour = new TimelineValue[]{new TimelineValue(0, start.r, start.g, start.b, start.a), new TimelineValue(particleLifetime, end.r, end.g, end.b, end.a)};
this.colour[0].setInterpolated(true, this.colour[1]);
this.velocity = new TimelineValue[]{new TimelineValue(0, vx, vy, vz)};
}
public void addLight(boolean isStatic, float attenuation, float power, Color colour, boolean flicker, float x, float y, float z)
{
this.lightAttenuation = attenuation;
this.lightPower = power;
this.isLightStatic = isStatic;
this.lightColour = colour;
this.lightFlicker = flicker;
this.lightx = x;
this.lighty = y;
this.lightz = z;
}
public void create() {
if (shader == null)
{
shader = new ShaderProgram(SHADER_VERTEX, SHADER_FRAGMENT);
}
ran = new Random();
quad = Pools.obtain(Vector3.class).set(0, 0, 0);
tmpMat = Pools.obtain(Matrix4.class).idt();
tmpRot = Pools.obtain(Matrix4.class).idt();
pos = Pools.obtain(Vector3.class).set(0, 0, 0);
calculateRadius();
reloadParticles();
reloadTextures();
}
public void dispose()
{
if (quad != null) Pools.free(quad);
quad = null;
if (tmpMat != null) Pools.free(tmpMat);
tmpMat = null;
if (tmpRot != null) Pools.free(tmpRot);
tmpRot = null;
if (pos != null) Pools.free(pos);
pos = null;
if (mesh != null) mesh.dispose();
mesh = null;
}
public void getLight(LightManager lightManager)
{
if (lightColour == null) return;
if (light != null)
{
if (isLightStatic) lightManager.removeStaticLight(light.UID);
else lightManager.removeDynamicLight(light.UID);
light = null;
}
light = new PointLight(new Vector3(x+lightx+(ex/2f), y+lighty+ey, z+lightz+(ez/2)), lightColour.cpy(), lightAttenuation, lightPower);
lightUID = light.UID;
if (isLightStatic) lightManager.addStaticLight(light);
else lightManager.addDynamicLight(light);
}
public void calculateParticles()
{
maxParticles = (int) ((float)particleLifetime / (float)emissionTime);
}
public void calculateRadius()
{
this.radius = ex+ey+ez;
}
public void reloadParticles()
{
active = new Bag<Particle>(maxParticles);
inactive = new Bag<Particle>(maxParticles);
for (int i = 0; i < maxParticles; i++)
{
Particle p = new Particle();
inactive.add(p);
}
vertices = new float[maxParticles*VERTEX_SIZE*4];
if (mesh != null) mesh.dispose();
mesh = new Mesh(false, maxParticles*4, maxParticles*6,
new VertexAttribute(Usage.Position, 3, "a_position"),
new VertexAttribute(Usage.Generic, 4, "a_colour"),
new VertexAttribute(Usage.TextureCoordinates, 2, "a_texCoords"));
mesh.setVertices(vertices);
mesh.setIndices(genIndices(maxParticles));
}
public void reloadTextures()
{
atlas = FileUtils.loadAtlas(atlasName);
Set<Texture> atlasTextures = atlas.getTextures();
Iterator<Texture> itr = atlasTextures.iterator();
atlasTexture = itr.next();
int maxIndex = 0;
for (TimelineValue spriteTL : sprite)
{
int index = (int) spriteTL.getValues()[0];
if (index > maxIndex) maxIndex = index;
}
topLeftTexCoords = new float[maxIndex+1][2];
topRightTexCoords = new float[maxIndex+1][2];
botLeftTexCoords = new float[maxIndex+1][2];
botRightTexCoords = new float[maxIndex+1][2];
for (int i = 0; i < maxIndex+1; i++)
{
AtlasRegion region = atlas.findRegion("sprite"+i);
float[] tl = {(float)region.getRegionX()/(float)atlasTexture.getWidth(), (float)region.getRegionY()/(float)atlasTexture.getHeight()};
float[] tr = {(float)(region.getRegionX()+region.getRegionWidth())/(float)atlasTexture.getWidth(), (float)region.getRegionY()/(float)atlasTexture.getHeight()};
float[] bl = {(float)region.getRegionX()/(float)atlasTexture.getWidth(), (float)(region.getRegionY()+region.getRegionHeight())/(float)atlasTexture.getHeight()};
float[] br = {(float)(region.getRegionX()+region.getRegionWidth())/(float)atlasTexture.getWidth(), (float)(region.getRegionY()+region.getRegionHeight())/(float)atlasTexture.getHeight()};
topLeftTexCoords[i] = tl;
topRightTexCoords[i] = tr;
botLeftTexCoords[i] = bl;
botRightTexCoords[i] = br;
}
}
public short[] genIndices(int faces)
{
short[] indices = new short[faces * 6];
for (short i = 0; i < faces; i++)
{
indices[(i*6)+0] = (short) ((i*4)+0);
indices[(i*6)+1] = (short) ((i*4)+1);
indices[(i*6)+2] = (short) ((i*4)+2);
indices[(i*6)+3] = (short) ((i*4)+1);
indices[(i*6)+4] = (short) ((i*4)+3);
indices[(i*6)+5] = (short) ((i*4)+2);
}
return indices;
}
public void render()
{
if (currentBlendSRC == blendFuncSRC && currentBlendDST == blendFuncDST)
{}
else {
Gdx.gl.glBlendFunc(blendFuncSRC, blendFuncDST);
currentBlendSRC = blendFuncSRC;
currentBlendDST = blendFuncDST;
}
if (currentAtlas != null && atlasName.equals(currentAtlas)) {
}
else
{
atlasTexture.bind(0);
shader.setUniformi("u_texture", 0);
currentAtlas = atlasName;
}
mesh.render(shader, GL20.GL_TRIANGLES, 0, active.size*4);
}
public static void begin(Camera cam)
{
Gdx.gl.glEnable(GL20.GL_BLEND);
Gdx.gl.glEnable(GL20.GL_DEPTH_TEST);
Gdx.gl.glDepthMask(false);
shader.begin();
shader.setUniformMatrix("u_pv", cam.combined);
}
public static void end()
{
shader.end();
Gdx.gl.glDepthMask(true);
Gdx.gl.glDisable(GL20.GL_BLEND);
currentAtlas = null;
currentBlendSRC = currentBlendDST = 0;
}
public void update(float delta, Camera cam)
{
if (light != null)
{
light.positionAbsolutely(x+lightx+(ex/2f), y+lighty+ey, z+lightz+(ez/2));
if (lightFlicker) light.attenuation = (float) (lightAttenuation *
(1-((1-((float)inactive.size / (float)active.size)))/2));
}
tmpRot.set(cam.view).inv();
tmpRot.getValues()[Matrix4.M03] = 0;
tmpRot.getValues()[Matrix4.M13] = 0;
tmpRot.getValues()[Matrix4.M23] = 0;
Iterator<Particle> pItr = active.iterator();
i = 0;
while (pItr.hasNext())
{
Particle p = pItr.next();
float[] velocity = getAttributeValue(p.lifetime, ParticleAttribute.VELOCITY);
p.update(delta, velocity[0], velocity[1], velocity[2]);
if (p.lifetime > particleLifetime)
{
pItr.remove();
inactive.add(p);
continue;
}
tmpMat.setToTranslation(p.x, p.y, p.z).mul(tmpRot);
int sprite = (int) getAttributeValue(p.lifetime, ParticleAttribute.SPRITE)[0];
float[] size = getAttributeValue(p.lifetime, ParticleAttribute.SIZE);
float[] colour = getAttributeValue(p.lifetime, ParticleAttribute.COLOUR);
quad
.set(-size[0]/2, size[1]/2, 0)
.mul(tmpMat);
v = 0;
vertices[(i*VERTEX_SIZE*4)+v+0] = quad.x;
vertices[(i*VERTEX_SIZE*4)+v+1] = quad.y;
vertices[(i*VERTEX_SIZE*4)+v+2] = quad.z;
vertices[(i*VERTEX_SIZE*4)+v+3] = colour[0];
vertices[(i*VERTEX_SIZE*4)+v+4] = colour[1];
vertices[(i*VERTEX_SIZE*4)+v+5] = colour[2];
vertices[(i*VERTEX_SIZE*4)+v+6] = colour[3];
vertices[(i*VERTEX_SIZE*4)+v+7] = topLeftTexCoords[sprite][0];
vertices[(i*VERTEX_SIZE*4)+v+8] = topLeftTexCoords[sprite][1];
quad
.set(size[0]/2, size[1]/2, 0)
.mul(tmpMat);
v += VERTEX_SIZE;
vertices[(i*VERTEX_SIZE*4)+v+0] = quad.x;
vertices[(i*VERTEX_SIZE*4)+v+1] = quad.y;
vertices[(i*VERTEX_SIZE*4)+v+2] = quad.z;
vertices[(i*VERTEX_SIZE*4)+v+3] = colour[0];
vertices[(i*VERTEX_SIZE*4)+v+4] = colour[1];
vertices[(i*VERTEX_SIZE*4)+v+5] = colour[2];
vertices[(i*VERTEX_SIZE*4)+v+6] = colour[3];
vertices[(i*VERTEX_SIZE*4)+v+7] = topRightTexCoords[sprite][0];
vertices[(i*VERTEX_SIZE*4)+v+8] = topRightTexCoords[sprite][1];
quad
.set(-size[0]/2, -size[1]/2, 0)
.mul(tmpMat);
v += VERTEX_SIZE;
vertices[(i*VERTEX_SIZE*4)+v+0] = quad.x;
vertices[(i*VERTEX_SIZE*4)+v+1] = quad.y;
vertices[(i*VERTEX_SIZE*4)+v+2] = quad.z;
vertices[(i*VERTEX_SIZE*4)+v+3] = colour[0];
vertices[(i*VERTEX_SIZE*4)+v+4] = colour[1];
vertices[(i*VERTEX_SIZE*4)+v+5] = colour[2];
vertices[(i*VERTEX_SIZE*4)+v+6] = colour[3];
vertices[(i*VERTEX_SIZE*4)+v+7] = botLeftTexCoords[sprite][0];
vertices[(i*VERTEX_SIZE*4)+v+8] = botLeftTexCoords[sprite][1];
quad
.set(size[0]/2, -size[1]/2, 0)
.mul(tmpMat);
v += VERTEX_SIZE;
vertices[(i*VERTEX_SIZE*4)+v+0] = quad.x;
vertices[(i*VERTEX_SIZE*4)+v+1] = quad.y;
vertices[(i*VERTEX_SIZE*4)+v+2] = quad.z;
vertices[(i*VERTEX_SIZE*4)+v+3] = colour[0];
vertices[(i*VERTEX_SIZE*4)+v+4] = colour[1];
vertices[(i*VERTEX_SIZE*4)+v+5] = colour[2];
vertices[(i*VERTEX_SIZE*4)+v+6] = colour[3];
vertices[(i*VERTEX_SIZE*4)+v+7] = botRightTexCoords[sprite][0];
vertices[(i*VERTEX_SIZE*4)+v+8] = botRightTexCoords[sprite][1];
i++;
}
mesh.setVertices(vertices);
emissionCD -= delta;
arrayLen = inactive.size;
if (arrayLen == 0) return;
while (emissionCD < 0 && arrayLen > 0)
{
Particle p = inactive.remove(0);
if (emissionType == 0)
{
signx = (ran.nextInt(2) == 0) ? 1 : -1;
signy = (ran.nextInt(2) == 0) ? 1 : -1;
signz = (ran.nextInt(2) == 0) ? 1 : -1;
p.set(particleLifetimeVar*ran.nextFloat(),
x+(float)(ex*ran.nextGaussian()*signx),
y+(float)(ey*ran.nextGaussian()*signy),
z+(float)(ez*ran.nextGaussian()*signz)
);
}
else
{
System.err.println("Invalid emission type! "+emissionType);
}
active.add(p);
emissionCD += emissionTime;
arrayLen--;
}
}
private float[] getAttributeValue(float time, ParticleAttribute pa) {
TimelineValue tv = null;
if (pa == ParticleAttribute.SPRITE)
{
tv = searchTimeline(time, sprite);
}
else if (pa == ParticleAttribute.SIZE)
{
tv = searchTimeline(time, size);
}
else if (pa == ParticleAttribute.COLOUR)
{
tv = searchTimeline(time, colour);
}
else if (pa == ParticleAttribute.VELOCITY)
{
tv = searchTimeline(time, velocity);
}
return tv.getValuesInterpolated(time);
}
private TimelineValue searchTimeline(float time, TimelineValue[] value)
{
arrayLen = value.length;
for (i2 = 0; i2 < arrayLen; i2++)
{
if (value[i2].time > time)
{
return value[i2-1];
}
}
return value[arrayLen-1];
}
public ParticleEmitter copy()
{
ParticleEmitter copy = new ParticleEmitter(particleLifetime, particleLifetimeVar, emissionTime, ex, ey, ez, emissionType, blendFuncSRC, blendFuncDST, atlasName, name);
copy.maxParticles = maxParticles;
TimelineValue[] cpySprite = new TimelineValue[sprite.length];
for (int i = 0; i < sprite.length; i++) cpySprite[i] = sprite[i].copy();
copy.sprite = cpySprite;
TimelineValue[] cpySize = new TimelineValue[size.length];
for (int i = 0; i < size.length; i++) cpySize[i] = size[i].copy();
copy.size = cpySize;
TimelineValue[] cpyColour = new TimelineValue[colour.length];
for (int i = 0; i < colour.length; i++) cpyColour[i] = colour[i].copy();
copy.colour = cpyColour;
TimelineValue[] cpyVelocity = new TimelineValue[velocity.length];
for (int i = 0; i < velocity.length; i++) cpyVelocity[i] = velocity[i].copy();
copy.velocity = cpyVelocity;
if (lightUID != null)
copy.addLight(isLightStatic, lightAttenuation, lightPower, lightColour, lightFlicker, lightx, lighty, lightz);
return copy;
}
/**
* A particle, containing its current lifetime and position.
* @author Philip
*
*/
class Particle {
float lifetime;
float x, y, z;
public Particle()
{
}
public void update(float delta, float vx, float vy, float vz)
{
lifetime += delta;
x += vx*delta;
y += vy*delta;
z += vz*delta;
}
public void set(float lifetime, float x, float y, float z)
{
this.lifetime = lifetime;
this.x = x;
this.y = y;
this.z = z;
}
}
public static class TimelineValue implements Serializable, Json.Serializable
{
private static final long serialVersionUID = -4434625075360858305L;
public float[] values;
public float time;
public boolean interpolated = false;
public float[] valueStep;
public float[] interpolatedValues;
private transient float timeStep;
private transient int arrayLen;
private transient int i;
public TimelineValue(){}
public TimelineValue(float time, float... values)
{
this.time = time;
this.values = values;
}
public float[] getValues()
{
return values;
}
protected void setValues(boolean interpolated, float[] valueStep, float[] interpolatedValues)
{
this.interpolated = interpolated;
this.valueStep = valueStep;
this.interpolatedValues = interpolatedValues;
}
public float[] getValuesInterpolated(float currentTime)
{
if (!interpolated)
{
return values;
}
timeStep = currentTime-time;
arrayLen = values.length;
for (i = 0; i < arrayLen; i++)
{
float value = values[i]+(valueStep[i]*timeStep);
interpolatedValues[i] = value;
}
return interpolatedValues;
}
public void setInterpolated(boolean interpolated, TimelineValue nextValue)
{
this.interpolated = interpolated;
if (interpolated)
{
interpolatedValues = new float[values.length];
valueStep = new float[values.length];
arrayLen = nextValue.values.length;
for (i = 0; i < arrayLen; i++)
{
float value = (nextValue.values[i] - values[i]) / (nextValue.time - time);
valueStep[i] = value;
}
}
}
public TimelineValue copy()
{
TimelineValue copy = new TimelineValue(time, values);
copy.setValues(interpolated, valueStep, interpolatedValues);
return copy;
}
public void write (Json json) {
json.writeValue("time", time);
json.writeValue("values", values);
json.writeValue("interpolated", interpolated);
if (interpolated) json.writeValue("value step", valueStep);
}
public void read (Json json, OrderedMap<String, Object> jsonMap) {
Iterator<Entry<String, Object>> itr = jsonMap.entries().iterator();
while (itr.hasNext())
{
Entry<String, Object> entry = itr.next();
if (entry.key.equals("time"))
{
time = (Float) entry.value;
}
else if (entry.key.equals("values"))
{
values = (float[]) entry.value;
}
else if (entry.key.equals("interpolated"))
{
interpolated = (Boolean) entry.value;
if (interpolated)
{
interpolatedValues = new float[values.length];
}
}
else if (entry.key.equals("value step"))
{
valueStep = (float[]) entry.value;
}
}
}
}
/**
* Write this particle emitter to the given json instance.
* @param json
*/
private void write (Json json) {
json.writeObjectStart();
json.writeValue("name", name);
json.writeValue("sprite", sprite);
json.writeValue("size", size);
json.writeValue("colour", colour);
json.writeValue("velocity", velocity);
json.writeValue("max particles", maxParticles);
json.writeValue("particle lifetime", particleLifetime);
json.writeValue("particle lifetime variance", particleLifetimeVar);
json.writeValue("emission time", emissionTime);
json.writeValue("emission x", ex);
json.writeValue("emission y", ey);
json.writeValue("emission z", ez);
json.writeValue("emission type", emissionType);
json.writeValue("blend func SRC", blendFuncSRC);
json.writeValue("blend func DST", blendFuncDST);
json.writeValue("atlas name", atlasName);
if (lightUID != null)
{
json.writeValue("light attenuation", lightAttenuation);
json.writeValue("light power", lightPower);
json.writeValue("light static", isLightStatic);
json.writeValue("light flicker", lightFlicker);
json.writeValue("light colour", lightColour);
json.writeValue("light offset x", lightx);
json.writeValue("light offset y", lighty);
json.writeValue("light offset z", lightz);
}
json.writeObjectEnd();
}
/**
* Get a json instance set up for reading and writing a particle emitter
* @return
*/
public static Json getJson (Json json) {
json.setSerializer(ParticleEmitter.class, new Json.Serializer<ParticleEmitter>() {
@SuppressWarnings("rawtypes")
public void write (Json json, ParticleEmitter emitter, Class knownType) {
emitter.write(json);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public ParticleEmitter read (Json json, Object jsonData, Class type) {
// ----- Particle Parameters ----- //
TimelineValue[] sprite = null;
TimelineValue[] size = null;
TimelineValue[] colour = null;
TimelineValue[] velocity = null;
// ----- End Particle Parameters ----- //
// ----- Emitter parameters ----- //
String name = null;
int maxParticles = 0;
float particleLifetime = 0;
float particleLifetimeVar = 0;
float emissionTime = 0;
float ex = 0, ey = 0, ez = 0;
int emissionType = 0;
int blendFuncSRC = 0;
int blendFuncDST = 0;
String atlasName = null;
// ----- End Emitter parameters ----- //
// ----- Light ----- //
float lightAttenuation = 0;
float lightPower = 0;
boolean isLightStatic = false;
Color lightColour = null;
boolean lightFlicker = false;
float lightx = 0, lighty = 0, lightz = 0;
// ----- End Light ----- //
OrderedMap<String, Object> jsonMap = (OrderedMap<String, Object>) jsonData;
Iterator<Entry<String, Object>> itr = jsonMap.entries();
while (itr.hasNext())
{
Entry<String, Object> entry = itr.next();
if (entry.key.equals("sprite"))
{
sprite = json.readValue(TimelineValue[].class, entry.value);
}
else if (entry.key.equals("size"))
{
size = json.readValue(TimelineValue[].class, entry.value);
}
else if (entry.key.equals("colour"))
{
colour = json.readValue(TimelineValue[].class, entry.value);
}
else if (entry.key.equals("velocity"))
{
velocity = json.readValue(TimelineValue[].class, entry.value);
}
else if (entry.key.equals("name"))
{
name = (String) entry.value;
}
else if (entry.key.equals("max particles"))
{
maxParticles = ((Float) entry.value).intValue();
}
else if (entry.key.equals("particle lifetime"))
{
particleLifetime = (Float) entry.value;
}
else if (entry.key.equals("particle lifetime variance"))
{
particleLifetimeVar = (Float) entry.value;
}
else if (entry.key.equals("emission time"))
{
emissionTime = (Float) entry.value;
}
else if (entry.key.equals("emission x"))
{
ex = (Float) entry.value;
}
else if (entry.key.equals("emission y"))
{
ey = (Float) entry.value;
}
else if (entry.key.equals("emission z"))
{
ez = (Float) entry.value;
}
else if (entry.key.equals("emission type"))
{
emissionType = ((Float) entry.value).intValue();
}
else if (entry.key.equals("blend func SRC"))
{
blendFuncSRC = ((Float) entry.value).intValue();
}
else if (entry.key.equals("blend func DST"))
{
blendFuncDST = ((Float) entry.value).intValue();
}
else if (entry.key.equals("atlas name"))
{
atlasName = (String) entry.value;
}
else if (entry.key.equals("light attenuation"))
{
lightAttenuation = (Float) entry.value;
}
else if (entry.key.equals("light power"))
{
lightPower = (Float) entry.value;
}
else if (entry.key.equals("light static"))
{
isLightStatic = (Boolean) entry.value;
}
else if (entry.key.equals("light flicker"))
{
lightFlicker = (Boolean) entry.value;
}
else if (entry.key.equals("light colour"))
{
lightColour = json.readValue(Color.class, entry.value);
}
else if (entry.key.equals("light offset x"))
{
lightx = (Float) entry.value;
}
else if (entry.key.equals("light offset y"))
{
lighty = (Float) entry.value;
}
else if (entry.key.equals("light offset z"))
{
lightz = (Float) entry.value;
}
}
ParticleEmitter emitter = new ParticleEmitter(particleLifetime, particleLifetimeVar, emissionTime,
ex, ey, ez,
emissionType,
blendFuncSRC, blendFuncDST,
atlasName, name);
emitter.maxParticles = maxParticles;
emitter.setTimeline(sprite, size, colour, velocity);
if (lightColour != null) emitter.addLight(isLightStatic, lightAttenuation, lightPower, lightColour, lightFlicker, lightx, lighty, lightz);
return emitter;
}
});
return json;
}
public static ParticleEmitter load (String file)
{
Json json = getJson(new Json());
return json.fromJson(ParticleEmitter.class, file);
}
public static ParticleEmitter load (FileHandle file)
{
Json json = getJson(new Json());
return json.fromJson(ParticleEmitter.class, file);
}
private static final String SHADER_VERTEX =
"attribute vec3 a_position;" + "\n" +
"attribute vec4 a_colour;" + "\n" +
"attribute vec2 a_texCoords;" + "\n" +
"uniform mat4 u_pv;" + "\n" +
"varying vec4 v_colour;" + "\n" +
"varying vec2 v_texCoords;" + "\n" +
"void main() {" + "\n" +
"v_colour = a_colour;" + "\n" +
"v_texCoords = a_texCoords;" + "\n" +
"gl_Position = u_pv * vec4(a_position, 1.0);" + "\n" +
"}";
private static final String SHADER_FRAGMENT =
"#ifdef GL_ES\n" +
"precision highp float;\n" +
"#endif\n" +
"uniform sampler2D u_texture;" + "\n" +
"varying vec4 v_colour;" + "\n" +
"varying vec2 v_texCoords;" + "\n" +
"void main() {" + "\n" +
"gl_FragColor = texture2D(u_texture, v_texCoords) * v_colour;" + "\n" +
"}";
private static final ParticleEmitterComparator comparator = new ParticleEmitterComparator();
public static Comparator<ParticleEmitter> getComparator()
{
return comparator;
}
static class ParticleEmitterComparator implements Comparator<ParticleEmitter>
{
public int compare(ParticleEmitter p1, ParticleEmitter p2) {
return (p1.distance < p2.distance) ? 1 : -1;
}
}
}