mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 03:24:06 +09:00
added sources for Slick
Former-commit-id: 1647fa32ef6894bd7db44f741f07c2f4dcdf9054 Former-commit-id: 0e5810dcfbe1fd59b13e7cabe9f1e93c5542da2d
This commit is contained in:
@@ -0,0 +1,911 @@
|
||||
package org.newdawn.slick.particles;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.geom.Vector2f;
|
||||
import org.newdawn.slick.util.FastTrig;
|
||||
import org.newdawn.slick.util.Log;
|
||||
|
||||
/**
|
||||
* An emitter than can be externally configured. This configuration can also be
|
||||
* saved/loaded using the ParticleIO class.
|
||||
*
|
||||
* @see ParticleIO
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class ConfigurableEmitter implements ParticleEmitter {
|
||||
/** The path from which the images should be loaded */
|
||||
private static String relativePath = "";
|
||||
|
||||
/**
|
||||
* Set the path from which images should be loaded
|
||||
*
|
||||
* @param path
|
||||
* The path from which images should be loaded
|
||||
*/
|
||||
public static void setRelativePath(String path) {
|
||||
if (!path.endsWith("/")) {
|
||||
path += "/";
|
||||
}
|
||||
relativePath = path;
|
||||
}
|
||||
|
||||
/** The spawn interval range property - how often spawn happens */
|
||||
public Range spawnInterval = new Range(100, 100);
|
||||
/** The spawn count property - how many particles are spawned each time */
|
||||
public Range spawnCount = new Range(5, 5);
|
||||
/** The initial life of the new pixels */
|
||||
public Range initialLife = new Range(1000, 1000);
|
||||
/** The initial size of the new pixels */
|
||||
public Range initialSize = new Range(10, 10);
|
||||
/** The offset from the x position */
|
||||
public Range xOffset = new Range(0, 0);
|
||||
/** The offset from the y position */
|
||||
public Range yOffset = new Range(0, 0);
|
||||
/** The spread of the particles */
|
||||
public RandomValue spread = new RandomValue(360);
|
||||
/** The angular offset */
|
||||
public SimpleValue angularOffset = new SimpleValue(0);
|
||||
/** The initial distance of the particles */
|
||||
public Range initialDistance = new Range(0, 0);
|
||||
/** The speed particles fly out */
|
||||
public Range speed = new Range(50, 50);
|
||||
/** The growth factor on the particles */
|
||||
public SimpleValue growthFactor = new SimpleValue(0);
|
||||
/** The factor of gravity to apply */
|
||||
public SimpleValue gravityFactor = new SimpleValue(0);
|
||||
/** The factor of wind to apply */
|
||||
public SimpleValue windFactor = new SimpleValue(0);
|
||||
/** The length of the effect */
|
||||
public Range length = new Range(1000, 1000);
|
||||
/**
|
||||
* The color range
|
||||
*
|
||||
* @see ColorRecord
|
||||
*/
|
||||
public ArrayList colors = new ArrayList();
|
||||
/** The starting alpha value */
|
||||
public SimpleValue startAlpha = new SimpleValue(255);
|
||||
/** The ending alpha value */
|
||||
public SimpleValue endAlpha = new SimpleValue(0);
|
||||
|
||||
/** Whiskas - Interpolated value for alpha */
|
||||
public LinearInterpolator alpha;
|
||||
/** Whiskas - Interpolated value for size */
|
||||
public LinearInterpolator size;
|
||||
/** Whiskas - Interpolated value for velocity */
|
||||
public LinearInterpolator velocity;
|
||||
/** Whiskas - Interpolated value for y axis scaling */
|
||||
public LinearInterpolator scaleY;
|
||||
|
||||
/** The number of particles that will be emitted */
|
||||
public Range emitCount = new Range(1000, 1000);
|
||||
/** The points indicate */
|
||||
public int usePoints = Particle.INHERIT_POINTS;
|
||||
|
||||
/** True if the quads should be orieted based on velocity */
|
||||
public boolean useOriented = false;
|
||||
/**
|
||||
* True if the additivie blending mode should be used for particles owned by
|
||||
* this emitter
|
||||
*/
|
||||
public boolean useAdditive = false;
|
||||
|
||||
/** The name attribute */
|
||||
public String name;
|
||||
/** The name of the image in use */
|
||||
public String imageName = "";
|
||||
/** The image being used for the particles */
|
||||
private Image image;
|
||||
/** True if the image needs updating */
|
||||
private boolean updateImage;
|
||||
|
||||
/** True if the emitter is enabled */
|
||||
private boolean enabled = true;
|
||||
/** The x coordinate of the position of this emitter */
|
||||
private float x;
|
||||
/** The y coordinate of the position of this emitter */
|
||||
private float y;
|
||||
/** The time in milliseconds til the next spawn */
|
||||
private int nextSpawn = 0;
|
||||
|
||||
/** The timeout counting down to spawn */
|
||||
private int timeout;
|
||||
/** The number of particles in use by this emitter */
|
||||
private int particleCount;
|
||||
/** The system this emitter is being updated to */
|
||||
private ParticleSystem engine;
|
||||
/** The number of particles that are left ot emit */
|
||||
private int leftToEmit;
|
||||
|
||||
/** True if we're wrapping up */
|
||||
protected boolean wrapUp = false;
|
||||
/** True if the system has completed due to a wrap up */
|
||||
protected boolean completed = false;
|
||||
/** True if we need to adjust particles for movement */
|
||||
protected boolean adjust;
|
||||
/** The amount to adjust on the x axis */
|
||||
protected float adjustx;
|
||||
/** The amount to adjust on the y axis */
|
||||
protected float adjusty;
|
||||
|
||||
/**
|
||||
* Create a new emitter configurable externally
|
||||
*
|
||||
* @param name
|
||||
* The name of emitter
|
||||
*/
|
||||
public ConfigurableEmitter(String name) {
|
||||
this.name = name;
|
||||
leftToEmit = (int) emitCount.random();
|
||||
timeout = (int) (length.random());
|
||||
|
||||
colors.add(new ColorRecord(0, Color.white));
|
||||
colors.add(new ColorRecord(1, Color.red));
|
||||
|
||||
ArrayList curve = new ArrayList();
|
||||
curve.add(new Vector2f(0.0f, 0.0f));
|
||||
curve.add(new Vector2f(1.0f, 255.0f));
|
||||
alpha = new LinearInterpolator(curve, 0, 255);
|
||||
|
||||
curve = new ArrayList();
|
||||
curve.add(new Vector2f(0.0f, 0.0f));
|
||||
curve.add(new Vector2f(1.0f, 255.0f));
|
||||
size = new LinearInterpolator(curve, 0, 255);
|
||||
|
||||
curve = new ArrayList();
|
||||
curve.add(new Vector2f(0.0f, 0.0f));
|
||||
curve.add(new Vector2f(1.0f, 1.0f));
|
||||
velocity = new LinearInterpolator(curve, 0, 1);
|
||||
|
||||
curve = new ArrayList();
|
||||
curve.add(new Vector2f(0.0f, 0.0f));
|
||||
curve.add(new Vector2f(1.0f, 1.0f));
|
||||
scaleY = new LinearInterpolator(curve, 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the name of the image to use on a per particle basis. The complete
|
||||
* reference to the image is required (based on the relative path)
|
||||
*
|
||||
* @see #setRelativePath(String)
|
||||
*
|
||||
* @param imageName
|
||||
* The name of the image to use on a per particle reference
|
||||
*/
|
||||
public void setImageName(String imageName) {
|
||||
if (imageName.length() == 0) {
|
||||
imageName = null;
|
||||
}
|
||||
|
||||
this.imageName = imageName;
|
||||
if (imageName == null) {
|
||||
image = null;
|
||||
} else {
|
||||
updateImage = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the image to load
|
||||
*
|
||||
* @return The name of the image to load
|
||||
*/
|
||||
public String getImageName() {
|
||||
return imageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return "[" + name + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position of this particle source
|
||||
*
|
||||
* @param x
|
||||
* The x coodinate of that this emitter should spawn at
|
||||
* @param y
|
||||
* The y coodinate of that this emitter should spawn at
|
||||
*/
|
||||
public void setPosition(float x, float y) {
|
||||
setPosition(x,y,true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position of this particle source
|
||||
*
|
||||
* @param x
|
||||
* The x coodinate of that this emitter should spawn at
|
||||
* @param y
|
||||
* The y coodinate of that this emitter should spawn at
|
||||
* @param moveParticles
|
||||
* True if particles should be moved with the emitter
|
||||
*/
|
||||
public void setPosition(float x, float y, boolean moveParticles) {
|
||||
if (moveParticles) {
|
||||
adjust = true;
|
||||
adjustx -= this.x - x;
|
||||
adjusty -= this.y - y;
|
||||
}
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base x coordiante for spawning particles
|
||||
*
|
||||
* @return The x coordinate for spawning particles
|
||||
*/
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base y coordiante for spawning particles
|
||||
*
|
||||
* @return The y coordinate for spawning particles
|
||||
*/
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#isEnabled()
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#setEnabled(boolean)
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#update(org.newdawn.slick.particles.ParticleSystem,
|
||||
* int)
|
||||
*/
|
||||
public void update(ParticleSystem system, int delta) {
|
||||
this.engine = system;
|
||||
|
||||
if (!adjust) {
|
||||
adjustx = 0;
|
||||
adjusty = 0;
|
||||
} else {
|
||||
adjust = false;
|
||||
}
|
||||
|
||||
if (updateImage) {
|
||||
updateImage = false;
|
||||
try {
|
||||
image = new Image(relativePath + imageName);
|
||||
} catch (SlickException e) {
|
||||
image = null;
|
||||
Log.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if ((wrapUp) ||
|
||||
((length.isEnabled()) && (timeout < 0)) ||
|
||||
((emitCount.isEnabled() && (leftToEmit <= 0)))) {
|
||||
if (particleCount == 0) {
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
particleCount = 0;
|
||||
|
||||
if (wrapUp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (length.isEnabled()) {
|
||||
if (timeout < 0) {
|
||||
return;
|
||||
}
|
||||
timeout -= delta;
|
||||
}
|
||||
if (emitCount.isEnabled()) {
|
||||
if (leftToEmit <= 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nextSpawn -= delta;
|
||||
if (nextSpawn < 0) {
|
||||
nextSpawn = (int) spawnInterval.random();
|
||||
int count = (int) spawnCount.random();
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
Particle p = system.getNewParticle(this, initialLife.random());
|
||||
p.setSize(initialSize.random());
|
||||
p.setPosition(x + xOffset.random(), y + yOffset.random());
|
||||
p.setVelocity(0, 0, 0);
|
||||
|
||||
float dist = initialDistance.random();
|
||||
float power = speed.random();
|
||||
if ((dist != 0) || (power != 0)) {
|
||||
float s = spread.getValue(0);
|
||||
float ang = (s + angularOffset.getValue(0) - (spread
|
||||
.getValue() / 2)) - 90;
|
||||
float xa = (float) FastTrig.cos(Math.toRadians(ang)) * dist;
|
||||
float ya = (float) FastTrig.sin(Math.toRadians(ang)) * dist;
|
||||
p.adjustPosition(xa, ya);
|
||||
|
||||
float xv = (float) FastTrig.cos(Math.toRadians(ang));
|
||||
float yv = (float) FastTrig.sin(Math.toRadians(ang));
|
||||
p.setVelocity(xv, yv, power * 0.001f);
|
||||
}
|
||||
|
||||
if (image != null) {
|
||||
p.setImage(image);
|
||||
}
|
||||
|
||||
ColorRecord start = (ColorRecord) colors.get(0);
|
||||
p.setColor(start.col.r, start.col.g, start.col.b, startAlpha
|
||||
.getValue(0) / 255.0f);
|
||||
p.setUsePoint(usePoints);
|
||||
p.setOriented(useOriented);
|
||||
|
||||
if (emitCount.isEnabled()) {
|
||||
leftToEmit--;
|
||||
if (leftToEmit <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#updateParticle(org.newdawn.slick.particles.Particle,
|
||||
* int)
|
||||
*/
|
||||
public void updateParticle(Particle particle, int delta) {
|
||||
particleCount++;
|
||||
|
||||
// adjust the particles if required
|
||||
particle.x += adjustx;
|
||||
particle.y += adjusty;
|
||||
|
||||
particle.adjustVelocity(windFactor.getValue(0) * 0.00005f * delta, gravityFactor
|
||||
.getValue(0) * 0.00005f * delta);
|
||||
|
||||
float offset = particle.getLife() / particle.getOriginalLife();
|
||||
float inv = 1 - offset;
|
||||
float colOffset = 0;
|
||||
float colInv = 1;
|
||||
|
||||
Color startColor = null;
|
||||
Color endColor = null;
|
||||
for (int i = 0; i < colors.size() - 1; i++) {
|
||||
ColorRecord rec1 = (ColorRecord) colors.get(i);
|
||||
ColorRecord rec2 = (ColorRecord) colors.get(i + 1);
|
||||
|
||||
if ((inv >= rec1.pos) && (inv <= rec2.pos)) {
|
||||
startColor = rec1.col;
|
||||
endColor = rec2.col;
|
||||
|
||||
float step = rec2.pos - rec1.pos;
|
||||
colOffset = inv - rec1.pos;
|
||||
colOffset /= step;
|
||||
colOffset = 1 - colOffset;
|
||||
colInv = 1 - colOffset;
|
||||
}
|
||||
}
|
||||
|
||||
if (startColor != null) {
|
||||
float r = (startColor.r * colOffset) + (endColor.r * colInv);
|
||||
float g = (startColor.g * colOffset) + (endColor.g * colInv);
|
||||
float b = (startColor.b * colOffset) + (endColor.b * colInv);
|
||||
|
||||
float a;
|
||||
if (alpha.isActive()) {
|
||||
a = alpha.getValue(inv) / 255.0f;
|
||||
} else {
|
||||
a = ((startAlpha.getValue(0) / 255.0f) * offset)
|
||||
+ ((endAlpha.getValue(0) / 255.0f) * inv);
|
||||
}
|
||||
particle.setColor(r, g, b, a);
|
||||
}
|
||||
|
||||
if (size.isActive()) {
|
||||
float s = size.getValue(inv);
|
||||
particle.setSize(s);
|
||||
} else {
|
||||
particle.adjustSize(delta * growthFactor.getValue(0) * 0.001f);
|
||||
}
|
||||
|
||||
if (velocity.isActive()) {
|
||||
particle.setSpeed(velocity.getValue(inv));
|
||||
}
|
||||
|
||||
if (scaleY.isActive()) {
|
||||
particle.setScaleY(scaleY.getValue(inv));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this emitter has completed it's cycle
|
||||
*
|
||||
* @return True if the emitter has completed it's cycle
|
||||
*/
|
||||
public boolean completed() {
|
||||
if (engine == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length.isEnabled()) {
|
||||
if (timeout > 0) {
|
||||
return false;
|
||||
}
|
||||
return completed;
|
||||
}
|
||||
if (emitCount.isEnabled()) {
|
||||
if (leftToEmit > 0) {
|
||||
return false;
|
||||
}
|
||||
return completed;
|
||||
}
|
||||
|
||||
if (wrapUp) {
|
||||
return completed;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cause the emitter to replay it's circle
|
||||
*/
|
||||
public void replay() {
|
||||
reset();
|
||||
nextSpawn = 0;
|
||||
leftToEmit = (int) emitCount.random();
|
||||
timeout = (int) (length.random());
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all the particles held by this emitter
|
||||
*/
|
||||
public void reset() {
|
||||
completed = false;
|
||||
if (engine != null) {
|
||||
engine.releaseAll(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the replay has died out - used by the editor
|
||||
*/
|
||||
public void replayCheck() {
|
||||
if (completed()) {
|
||||
if (engine != null) {
|
||||
if (engine.getParticleCount() == 0) {
|
||||
replay();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a duplicate of this emitter.
|
||||
* The duplicate should be added to a ParticleSystem to be used.
|
||||
* @return a copy if no IOException occurred, null otherwise
|
||||
*/
|
||||
public ConfigurableEmitter duplicate() {
|
||||
ConfigurableEmitter theCopy = null;
|
||||
try {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
ParticleIO.saveEmitter(bout, this);
|
||||
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
|
||||
theCopy = ParticleIO.loadEmitter(bin);
|
||||
} catch (IOException e) {
|
||||
Log.error("Slick: ConfigurableEmitter.duplicate(): caught exception " + e.toString());
|
||||
return null;
|
||||
}
|
||||
return theCopy;
|
||||
}
|
||||
|
||||
/**
|
||||
* a general interface to provide a general value :]
|
||||
*
|
||||
* @author void
|
||||
*/
|
||||
public interface Value {
|
||||
/**
|
||||
* get the current value that might depend from the given time
|
||||
*
|
||||
* @param time
|
||||
* @return the current value
|
||||
*/
|
||||
public float getValue(float time);
|
||||
}
|
||||
|
||||
/**
|
||||
* A configurable simple single value
|
||||
*
|
||||
* @author void
|
||||
*/
|
||||
public class SimpleValue implements Value {
|
||||
/** The value configured */
|
||||
private float value;
|
||||
/** The next value */
|
||||
private float next;
|
||||
|
||||
/**
|
||||
* Create a new configurable new value
|
||||
*
|
||||
* @param value
|
||||
* The initial value
|
||||
*/
|
||||
private SimpleValue(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently configured value
|
||||
*
|
||||
* @return The currently configured value
|
||||
*/
|
||||
public float getValue(float time) {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configured value
|
||||
*
|
||||
* @param value
|
||||
* The configured value
|
||||
*/
|
||||
public void setValue(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A configurable simple linear random value
|
||||
*
|
||||
* @author void
|
||||
*/
|
||||
public class RandomValue implements Value {
|
||||
/** The value configured */
|
||||
private float value;
|
||||
|
||||
/**
|
||||
* Create a new configurable new value
|
||||
*
|
||||
* @param value
|
||||
* The initial value
|
||||
*/
|
||||
private RandomValue(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently configured value
|
||||
*
|
||||
* @return The currently configured value
|
||||
*/
|
||||
public float getValue(float time) {
|
||||
return (float) (Math.random() * value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the configured value
|
||||
*
|
||||
* @param value
|
||||
* The configured value
|
||||
*/
|
||||
public void setValue(float value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the configured value
|
||||
*
|
||||
* @return the configured value
|
||||
*/
|
||||
public float getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A value computed based on linear interpolation between a set of points
|
||||
*
|
||||
* @author void
|
||||
*/
|
||||
public class LinearInterpolator implements Value {
|
||||
/** The list of points to interpolate between */
|
||||
private ArrayList curve;
|
||||
/** True if this interpolation value is active */
|
||||
private boolean active;
|
||||
/** The minimum value in the data set */
|
||||
private int min;
|
||||
/** The maximum value in the data set */
|
||||
private int max;
|
||||
|
||||
/**
|
||||
* Create a new interpolated value
|
||||
*
|
||||
* @param curve The set of points to interpolate between
|
||||
* @param min The minimum value in the dataset
|
||||
* @param max The maximum value possible in the dataset
|
||||
*/
|
||||
public LinearInterpolator(ArrayList curve, int min, int max) {
|
||||
this.curve = curve;
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the collection of data points to interpolate between
|
||||
*
|
||||
* @param curve The list of data points to interpolate between
|
||||
*/
|
||||
public void setCurve(ArrayList curve) {
|
||||
this.curve = curve;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of data points to interpolate between
|
||||
*
|
||||
* @return A list of Vector2f of the data points to interpolate between
|
||||
*/
|
||||
public ArrayList getCurve() {
|
||||
return curve;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value to use at a given time value
|
||||
*
|
||||
* @param t The time value (expecting t in [0,1])
|
||||
* @return The value to use at the specified time
|
||||
*/
|
||||
public float getValue(float t) {
|
||||
// first: determine the segment we are in
|
||||
Vector2f p0 = (Vector2f) curve.get(0);
|
||||
for (int i = 1; i < curve.size(); i++) {
|
||||
Vector2f p1 = (Vector2f) curve.get(i);
|
||||
|
||||
if (t >= p0.getX() && t <= p1.getX()) {
|
||||
// found the segment
|
||||
float st = (t - p0.getX())
|
||||
/ (p1.getX() - p0.getX());
|
||||
float r = p0.getY() + st
|
||||
* (p1.getY() - p0.getY());
|
||||
// System.out.println( "t: " + t + ", " + p0.x + ", " + p0.y
|
||||
// + " : " + p1.x + ", " + p1.y + " => " + r );
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
p0 = p1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this interpolated value should be used
|
||||
*
|
||||
* @return True if this value is in use
|
||||
*/
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if this interpoalte value should be used
|
||||
*
|
||||
* @param active True if this value should be used
|
||||
*/
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maxmimum value possible in this data set
|
||||
*
|
||||
* @return The maximum value possible in this data set
|
||||
*/
|
||||
public int getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maximum value possible in this data set
|
||||
*
|
||||
* @param max The maximum value possible in this data set
|
||||
*/
|
||||
public void setMax(int max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum value possible in this data set
|
||||
*
|
||||
* @return The minimum value possible in this data set
|
||||
*/
|
||||
public int getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum value possible in this data set
|
||||
*
|
||||
* @param min The minimum value possible in this data set
|
||||
*/
|
||||
public void setMin(int min) {
|
||||
this.min = min;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single element in the colour range of this emitter
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class ColorRecord {
|
||||
/** The position in the life cycle */
|
||||
public float pos;
|
||||
/** The color at this position */
|
||||
public Color col;
|
||||
|
||||
/**
|
||||
* Create a new record
|
||||
*
|
||||
* @param pos
|
||||
* The position in the life cycle (0 = start, 1 = end)
|
||||
* @param col
|
||||
* The color applied at this position
|
||||
*/
|
||||
public ColorRecord(float pos, Color col) {
|
||||
this.pos = pos;
|
||||
this.col = col;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a point in the colour cycle
|
||||
*
|
||||
* @param pos
|
||||
* The position in the life cycle (0 = start, 1 = end)
|
||||
* @param col
|
||||
* The color applied at this position
|
||||
*/
|
||||
public void addColorPoint(float pos, Color col) {
|
||||
colors.add(new ColorRecord(pos, col));
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple bean describing a range of values
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class Range {
|
||||
/** The maximum value in the range */
|
||||
private float max;
|
||||
/** The minimum value in the range */
|
||||
private float min;
|
||||
/** True if this range application is enabled */
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* Create a new configurable range
|
||||
*
|
||||
* @param min
|
||||
* The minimum value of the range
|
||||
* @param max
|
||||
* The maximum value of the range
|
||||
*/
|
||||
private Range(float min, float max) {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random number in the range
|
||||
*
|
||||
* @return The random number from the range
|
||||
*/
|
||||
public float random() {
|
||||
return (float) (min + (Math.random() * (max - min)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this configuration option is enabled
|
||||
*
|
||||
* @return True if the range is enabled
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if this option should be enabled
|
||||
*
|
||||
* @param enabled
|
||||
* True if this option should be enabled
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum value for this range
|
||||
*
|
||||
* @return The maximum value for this range
|
||||
*/
|
||||
public float getMax() {
|
||||
return max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the maxmium value for this range
|
||||
*
|
||||
* @param max
|
||||
* The maximum value for this range
|
||||
*/
|
||||
public void setMax(float max) {
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum value for this range
|
||||
*
|
||||
* @return The minimum value for this range
|
||||
*/
|
||||
public float getMin() {
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the minimum value for this range
|
||||
*
|
||||
* @param min
|
||||
* The minimum value for this range
|
||||
*/
|
||||
public void setMin(float min) {
|
||||
this.min = min;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean useAdditive() {
|
||||
return useAdditive;
|
||||
}
|
||||
|
||||
public boolean isOriented() {
|
||||
return this.useOriented;
|
||||
}
|
||||
|
||||
public boolean usePoints(ParticleSystem system) {
|
||||
return (this.usePoints == Particle.INHERIT_POINTS) && (system.usePoints()) ||
|
||||
(this.usePoints == Particle.USE_POINTS);
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void wrapUp() {
|
||||
wrapUp = true;
|
||||
}
|
||||
|
||||
public void resetState() {
|
||||
wrapUp = false;
|
||||
replay();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.newdawn.slick.particles;
|
||||
|
||||
/**
|
||||
* A description of any class that can create Configurable Emitters during the ParticleIO
|
||||
* load phase.
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public interface ConfigurableEmitterFactory {
|
||||
/**
|
||||
* Create a configurable emitter that will be populated with
|
||||
* loaded data
|
||||
*
|
||||
* @param name The name given to the emitter
|
||||
* @return The newly created configurable emitter
|
||||
*/
|
||||
public ConfigurableEmitter createEmitter(String name);
|
||||
}
|
||||
502
lib/slick-source/org/newdawn/slick/particles/Particle.java
Normal file
502
lib/slick-source/org/newdawn/slick/particles/Particle.java
Normal file
@@ -0,0 +1,502 @@
|
||||
package org.newdawn.slick.particles;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.opengl.TextureImpl;
|
||||
import org.newdawn.slick.opengl.renderer.Renderer;
|
||||
import org.newdawn.slick.opengl.renderer.SGL;
|
||||
|
||||
/**
|
||||
* A single particle within a system
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class Particle {
|
||||
/** The renderer to use for all GL operations */
|
||||
protected static SGL GL = Renderer.get();
|
||||
|
||||
/** Indicates the particle should inherit it's use of points */
|
||||
public static final int INHERIT_POINTS = 1;
|
||||
/** Indicates the particle should explicitly use points */
|
||||
public static final int USE_POINTS = 2;
|
||||
/** Indicates the particle should explicitly not use points */
|
||||
public static final int USE_QUADS = 3;
|
||||
|
||||
/** The x coordinate of the particle */
|
||||
protected float x;
|
||||
/** The y coordinate of the particle */
|
||||
protected float y;
|
||||
/** The x component of the direction vector of the particle */
|
||||
protected float velx;
|
||||
/** The y component of the direction vector of the particle */
|
||||
protected float vely;
|
||||
/** The current size in pixels of the particle */
|
||||
protected float size = 10;
|
||||
/** The colour of the particle */
|
||||
protected Color color = Color.white;
|
||||
/** The life left in the particle */
|
||||
protected float life;
|
||||
/** The original life of this particle */
|
||||
protected float originalLife;
|
||||
/** The engine this particle belongs to */
|
||||
private ParticleSystem engine;
|
||||
/** The emitter controllng this particle */
|
||||
private ParticleEmitter emitter;
|
||||
/** The image for this particle */
|
||||
protected Image image;
|
||||
/** The type identifier of this particle */
|
||||
protected int type;
|
||||
/** How this particle should be rendered */
|
||||
protected int usePoints = INHERIT_POINTS;
|
||||
/** True if this particle's quad should be oritented based on it's direction */
|
||||
protected boolean oriented = false;
|
||||
/** The currently scalar applied on the y axis */
|
||||
protected float scaleY = 1.0f;
|
||||
|
||||
/**
|
||||
* Create a new particle belonging to given engine
|
||||
*
|
||||
* @param engine
|
||||
* The engine the new particle belongs to
|
||||
*/
|
||||
public Particle(ParticleSystem engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x offset of this particle
|
||||
*
|
||||
* @return The x offset of this particle
|
||||
*/
|
||||
public float getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y offset of this particle
|
||||
*
|
||||
* @return The y offset of this particle
|
||||
*/
|
||||
public float getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this particle a fixed amount
|
||||
*
|
||||
* @param x The amount to move the particle on the horizontal axis
|
||||
* @param y The amount to move the particle on the vertical axis
|
||||
*/
|
||||
public void move(float x, float y) {
|
||||
this.x += x;
|
||||
this.y += y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of this particle
|
||||
*
|
||||
* @return The size of this particle
|
||||
*/
|
||||
public float getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the color of this particle
|
||||
*
|
||||
* @return The color of this particle
|
||||
*/
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the image used to render this particle
|
||||
*
|
||||
* @param image
|
||||
* The image used to render this particle
|
||||
*/
|
||||
public void setImage(Image image) {
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original life of this particle
|
||||
*
|
||||
* @return The original life of this particle
|
||||
*/
|
||||
public float getOriginalLife() {
|
||||
return originalLife;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the life remaining in the particle in milliseconds
|
||||
*
|
||||
* @return The life remaining in the particle
|
||||
*/
|
||||
public float getLife() {
|
||||
return life;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this particle is currently in use (i.e. is it rendering?)
|
||||
*
|
||||
* @return True if the particle is currently in use
|
||||
*/
|
||||
public boolean inUse() {
|
||||
return life > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render this particle
|
||||
*/
|
||||
public void render() {
|
||||
if ((engine.usePoints() && (usePoints == INHERIT_POINTS))
|
||||
|| (usePoints == USE_POINTS)) {
|
||||
TextureImpl.bindNone();
|
||||
GL.glEnable(SGL.GL_POINT_SMOOTH);
|
||||
GL.glPointSize(size / 2);
|
||||
color.bind();
|
||||
GL.glBegin(SGL.GL_POINTS);
|
||||
GL.glVertex2f(x, y);
|
||||
GL.glEnd();
|
||||
} else if (oriented || scaleY != 1.0f) {
|
||||
GL.glPushMatrix();
|
||||
|
||||
GL.glTranslatef(x, y, 0f);
|
||||
|
||||
if (oriented) {
|
||||
float angle = (float) (Math.atan2(y, x) * 180 / Math.PI);
|
||||
GL.glRotatef(angle, 0f, 0f, 1.0f);
|
||||
}
|
||||
|
||||
// scale
|
||||
GL.glScalef(1.0f, scaleY, 1.0f);
|
||||
|
||||
image.draw((int) (-(size / 2)), (int) (-(size / 2)), (int) size,
|
||||
(int) size, color);
|
||||
GL.glPopMatrix();
|
||||
} else {
|
||||
color.bind();
|
||||
image.drawEmbedded((int) (x - (size / 2)), (int) (y - (size / 2)),
|
||||
(int) size, (int) size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state of this particle
|
||||
*
|
||||
* @param delta
|
||||
* The time since the last update
|
||||
*/
|
||||
public void update(int delta) {
|
||||
emitter.updateParticle(this, delta);
|
||||
life -= delta;
|
||||
|
||||
if (life > 0) {
|
||||
x += delta * velx;
|
||||
y += delta * vely;
|
||||
} else {
|
||||
engine.release(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the state of the particle as it's reused
|
||||
*
|
||||
* @param emitter
|
||||
* The emitter controlling this particle
|
||||
* @param life
|
||||
* The life the particle should have (in milliseconds)
|
||||
*/
|
||||
public void init(ParticleEmitter emitter, float life) {
|
||||
x = 0;
|
||||
this.emitter = emitter;
|
||||
y = 0;
|
||||
velx = 0;
|
||||
vely = 0;
|
||||
size = 10;
|
||||
type = 0;
|
||||
this.originalLife = this.life = life;
|
||||
oriented = false;
|
||||
scaleY = 1.0f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of this particle
|
||||
*
|
||||
* @param type
|
||||
* The type of this particle
|
||||
*/
|
||||
public void setType(int type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate how this particle should be renered
|
||||
*
|
||||
* @param usePoints
|
||||
* The indicator for rendering
|
||||
* @see #USE_POINTS
|
||||
* @see #USE_QUADS
|
||||
* @see #INHERIT_POINTS
|
||||
*/
|
||||
public void setUsePoint(int usePoints) {
|
||||
this.usePoints = usePoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of this particle
|
||||
*
|
||||
* @return The type of this particle
|
||||
*/
|
||||
public int getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the size of the particle
|
||||
*
|
||||
* @param size
|
||||
* The size of the particle (in pixels)
|
||||
*/
|
||||
public void setSize(float size) {
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the size of the particle
|
||||
*
|
||||
* @param delta
|
||||
* The amount to adjust the size by (in pixels)
|
||||
*/
|
||||
public void adjustSize(float delta) {
|
||||
size += delta;
|
||||
size = Math.max(0, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the life of the particle
|
||||
*
|
||||
* @param life
|
||||
* The life of the particle in milliseconds
|
||||
*/
|
||||
public void setLife(float life) {
|
||||
this.life = life;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the life othe particle
|
||||
*
|
||||
* @param delta
|
||||
* The amount to adjust the particle by (in milliseconds)
|
||||
*/
|
||||
public void adjustLife(float delta) {
|
||||
life += delta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill the particle, stop it rendering and send it back to the engine for
|
||||
* use.
|
||||
*/
|
||||
public void kill() {
|
||||
life = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color of the particle
|
||||
*
|
||||
* @param r
|
||||
* The red component of the color
|
||||
* @param g
|
||||
* The green component of the color
|
||||
* @param b
|
||||
* The blue component of the color
|
||||
* @param a
|
||||
* The alpha component of the color
|
||||
*/
|
||||
public void setColor(float r, float g, float b, float a) {
|
||||
if (color == Color.white) {
|
||||
color = new Color(r,g,b,a);
|
||||
} else {
|
||||
color.r = r;
|
||||
color.g = g;
|
||||
color.b = b;
|
||||
color.a = a;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position of this particle
|
||||
*
|
||||
* @param x
|
||||
* The new x position of the particle
|
||||
* @param y
|
||||
* The new y position of the particle
|
||||
*/
|
||||
public void setPosition(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the velocity of the particle
|
||||
*
|
||||
* @param dirx
|
||||
* The x component of the new velocity
|
||||
* @param diry
|
||||
* The y component of the new velocity
|
||||
* @param speed
|
||||
* The speed in the given direction
|
||||
*/
|
||||
public void setVelocity(float dirx, float diry, float speed) {
|
||||
this.velx = dirx * speed;
|
||||
this.vely = diry * speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current speed of this particle
|
||||
*
|
||||
* @param speed The speed of this particle
|
||||
*/
|
||||
public void setSpeed(float speed) {
|
||||
float currentSpeed = (float) Math.sqrt((velx*velx) + (vely*vely));
|
||||
velx *= speed;
|
||||
vely *= speed;
|
||||
velx /= currentSpeed;
|
||||
vely /= currentSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the velocity of the particle
|
||||
*
|
||||
* @param velx The x component of the new velocity
|
||||
* @param vely The y component of the new velocity
|
||||
*/
|
||||
public void setVelocity(float velx, float vely) {
|
||||
setVelocity(velx,vely,1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust (add) the position of this particle
|
||||
*
|
||||
* @param dx
|
||||
* The amount to adjust the x component by
|
||||
* @param dy
|
||||
* The amount to adjust the y component by
|
||||
*/
|
||||
public void adjustPosition(float dx, float dy) {
|
||||
x += dx;
|
||||
y += dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust (add) the color of the particle
|
||||
*
|
||||
* @param r
|
||||
* The amount to adjust the red component by
|
||||
* @param g
|
||||
* The amount to adjust the green component by
|
||||
* @param b
|
||||
* The amount to adjust the blue component by
|
||||
* @param a
|
||||
* The amount to adjust the alpha component by
|
||||
*/
|
||||
public void adjustColor(float r, float g, float b, float a) {
|
||||
if (color == Color.white) {
|
||||
color = new Color(1,1,1,1f);
|
||||
}
|
||||
color.r += r;
|
||||
color.g += g;
|
||||
color.b += b;
|
||||
color.a += a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust (add) the color of the particle
|
||||
*
|
||||
* @param r
|
||||
* The amount to adjust the red component by
|
||||
* @param g
|
||||
* The amount to adjust the green component by
|
||||
* @param b
|
||||
* The amount to adjust the blue component by
|
||||
* @param a
|
||||
* The amount to adjust the alpha component by
|
||||
*/
|
||||
public void adjustColor(int r, int g, int b, int a) {
|
||||
if (color == Color.white) {
|
||||
color = new Color(1,1,1,1f);
|
||||
}
|
||||
|
||||
color.r += (r / 255.0f);
|
||||
color.g += (g / 255.0f);
|
||||
color.b += (b / 255.0f);
|
||||
color.a += (a / 255.0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust (add) the direction of this particle
|
||||
*
|
||||
* @param dx
|
||||
* The amount to adjust the x component by
|
||||
* @param dy
|
||||
* The amount to adjust the y component by
|
||||
*/
|
||||
public void adjustVelocity(float dx, float dy) {
|
||||
velx += dx;
|
||||
vely += dy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the emitter that owns this particle
|
||||
*
|
||||
* @return The emitter that owns this particle
|
||||
*/
|
||||
public ParticleEmitter getEmitter() {
|
||||
return emitter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
public String toString() {
|
||||
return super.toString() + " : " + life;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this particle is being oriented based on it's velocity
|
||||
*
|
||||
* @return True if this particle being oriented based on it's velocity
|
||||
*/
|
||||
public boolean isOriented() {
|
||||
return oriented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if this particle should be oriented based on it's velocity
|
||||
*
|
||||
* @param oriented True if this particle is being oriented based on it's velocity
|
||||
*/
|
||||
public void setOriented(boolean oriented) {
|
||||
this.oriented = oriented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current scalar applied on the y axis
|
||||
*
|
||||
* @return The scalar applied on the y axis
|
||||
*/
|
||||
public float getScaleY() {
|
||||
return scaleY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current scalar applied on the y axis
|
||||
*
|
||||
* @param scaleY The new scalar to apply on the y axis
|
||||
*/
|
||||
public void setScaleY(float scaleY) {
|
||||
this.scaleY = scaleY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package org.newdawn.slick.particles;
|
||||
|
||||
import org.newdawn.slick.Image;
|
||||
|
||||
/**
|
||||
* An emitter is responsible for producing the particles and controlling them during
|
||||
* their life. An implementation of this interface can be considered a particle
|
||||
* effect.
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public interface ParticleEmitter {
|
||||
/**
|
||||
* Update the emitter, produce any particles required by requesting
|
||||
* them from the particle system provided.
|
||||
*
|
||||
* @param system The particle system used to create particles
|
||||
* @param delta The amount of time in milliseconds since last emitter update
|
||||
*/
|
||||
public void update(ParticleSystem system, int delta);
|
||||
|
||||
/**
|
||||
* Check if this emitter has completed it's cycle
|
||||
*
|
||||
* @return True if the emitter has completed it's cycle
|
||||
*/
|
||||
public boolean completed();
|
||||
|
||||
/**
|
||||
* Wrap up the particle emitter. This means the emitter will no longer produce
|
||||
* particles and will be marked as completed once the particles have expired
|
||||
*/
|
||||
public void wrapUp();
|
||||
|
||||
/**
|
||||
* Update a single particle that this emitter produced
|
||||
*
|
||||
* @param particle The particle to be updated
|
||||
* @param delta The amount of time in millisecond since last particle update
|
||||
*/
|
||||
public void updateParticle(Particle particle, int delta);
|
||||
|
||||
/**
|
||||
* Check if the emitter is enabled
|
||||
*
|
||||
* @return True if the emitter is enabled
|
||||
*/
|
||||
public boolean isEnabled();
|
||||
|
||||
/**
|
||||
* Indicate whether the emitter should be enabled
|
||||
*
|
||||
* @param enabled True if the emitter should be enabled
|
||||
*/
|
||||
public void setEnabled(boolean enabled);
|
||||
|
||||
/**
|
||||
* Check if this emitter should use additive blending
|
||||
*
|
||||
* @return True if the emitter should use the right blending
|
||||
*/
|
||||
public boolean useAdditive();
|
||||
|
||||
/**
|
||||
* Get the image to draw for each particle
|
||||
*
|
||||
* @return The image to draw for each particle
|
||||
*/
|
||||
public Image getImage();
|
||||
|
||||
/**
|
||||
* Check if the particles produced should maintain orientation
|
||||
*
|
||||
* @return True if the particles produced should maintain orientation
|
||||
*/
|
||||
public boolean isOriented();
|
||||
|
||||
/**
|
||||
* Check if this emitter should use points based on it's own settings
|
||||
* and those of the particle system
|
||||
*
|
||||
* @param system The particle system to cross check agianst
|
||||
* @return True if we should use points
|
||||
*/
|
||||
public boolean usePoints(ParticleSystem system);
|
||||
|
||||
/**
|
||||
* Clear the state of emitter back to default
|
||||
*/
|
||||
public void resetState();
|
||||
}
|
||||
817
lib/slick-source/org/newdawn/slick/particles/ParticleIO.java
Normal file
817
lib/slick-source/org/newdawn/slick/particles/ParticleIO.java
Normal file
@@ -0,0 +1,817 @@
|
||||
package org.newdawn.slick.particles;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.transform.OutputKeys;
|
||||
import javax.xml.transform.Result;
|
||||
import javax.xml.transform.Transformer;
|
||||
import javax.xml.transform.TransformerFactory;
|
||||
import javax.xml.transform.dom.DOMSource;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.geom.Vector2f;
|
||||
import org.newdawn.slick.particles.ConfigurableEmitter.ColorRecord;
|
||||
import org.newdawn.slick.particles.ConfigurableEmitter.LinearInterpolator;
|
||||
import org.newdawn.slick.particles.ConfigurableEmitter.RandomValue;
|
||||
import org.newdawn.slick.particles.ConfigurableEmitter.SimpleValue;
|
||||
import org.newdawn.slick.util.Log;
|
||||
import org.newdawn.slick.util.ResourceLoader;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
* Utility methods to (de)serialize ConfigureEmitters to and from XML
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class ParticleIO {
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The reference to the XML file (file or classpath)
|
||||
* @param mask
|
||||
* @return A configured particle system
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(String ref, Color mask)
|
||||
throws IOException {
|
||||
return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),
|
||||
null, null, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The reference to the XML file (file or classpath)
|
||||
* @return A configured particle system
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(String ref)
|
||||
throws IOException {
|
||||
return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The XML file to read
|
||||
* @return A configured particle system
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(File ref)
|
||||
throws IOException {
|
||||
return loadConfiguredSystem(new FileInputStream(ref), null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The stream to read the XML from
|
||||
* @param mask The mask used to make the particle image transparent
|
||||
* @return A configured particle system
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(InputStream ref, Color mask)
|
||||
throws IOException {
|
||||
return loadConfiguredSystem(ref, null, null, mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The stream to read the XML from
|
||||
* @return A configured particle system
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(InputStream ref)
|
||||
throws IOException {
|
||||
return loadConfiguredSystem(ref, null, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The reference to the XML file (file or classpath)
|
||||
* @return A configured particle system
|
||||
* @param factory
|
||||
* The factory used to create the emitter than will be poulated
|
||||
* with loaded data.
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(String ref,
|
||||
ConfigurableEmitterFactory factory) throws IOException {
|
||||
return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),
|
||||
factory, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The XML file to read
|
||||
* @return A configured particle system
|
||||
* @param factory
|
||||
* The factory used to create the emitter than will be poulated
|
||||
* with loaded data.
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(File ref,
|
||||
ConfigurableEmitterFactory factory) throws IOException {
|
||||
return loadConfiguredSystem(new FileInputStream(ref), factory, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The stream to read the XML from
|
||||
* @return A configured particle system
|
||||
* @param factory
|
||||
* The factory used to create the emitter than will be poulated
|
||||
* with loaded data.
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(InputStream ref,
|
||||
ConfigurableEmitterFactory factory) throws IOException {
|
||||
return loadConfiguredSystem(ref, factory, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a set of configured emitters into a single system
|
||||
*
|
||||
* @param ref
|
||||
* The stream to read the XML from
|
||||
* @param factory
|
||||
* The factory used to create the emitter than will be poulated
|
||||
* with loaded data.
|
||||
* @param system The particle system that will be loaded into
|
||||
* @param mask The mask used to make the image background transparent
|
||||
* @return A configured particle system
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ParticleSystem loadConfiguredSystem(InputStream ref,
|
||||
ConfigurableEmitterFactory factory, ParticleSystem system, Color mask) throws IOException {
|
||||
if (factory == null) {
|
||||
factory = new ConfigurableEmitterFactory() {
|
||||
public ConfigurableEmitter createEmitter(String name) {
|
||||
return new ConfigurableEmitter(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
try {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder();
|
||||
Document document = builder.parse(ref);
|
||||
|
||||
Element element = document.getDocumentElement();
|
||||
if (!element.getNodeName().equals("system")) {
|
||||
throw new IOException("Not a particle system file");
|
||||
}
|
||||
|
||||
if (system == null) {
|
||||
system = new ParticleSystem("org/newdawn/slick/data/particle.tga",
|
||||
2000, mask);
|
||||
}
|
||||
boolean additive = "true".equals(element.getAttribute("additive"));
|
||||
if (additive) {
|
||||
system.setBlendingMode(ParticleSystem.BLEND_ADDITIVE);
|
||||
} else {
|
||||
system.setBlendingMode(ParticleSystem.BLEND_COMBINE);
|
||||
}
|
||||
boolean points = "true".equals(element.getAttribute("points"));
|
||||
system.setUsePoints(points);
|
||||
|
||||
NodeList list = element.getElementsByTagName("emitter");
|
||||
for (int i = 0; i < list.getLength(); i++) {
|
||||
Element em = (Element) list.item(i);
|
||||
ConfigurableEmitter emitter = factory.createEmitter("new");
|
||||
elementToEmitter(em, emitter);
|
||||
|
||||
system.addEmitter(emitter);
|
||||
}
|
||||
|
||||
system.setRemoveCompletedEmitters(false);
|
||||
return system;
|
||||
} catch (IOException e) {
|
||||
Log.error(e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
Log.error(e);
|
||||
throw new IOException("Unable to load particle system config");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a particle system with only ConfigurableEmitters in to an XML file
|
||||
*
|
||||
* @param file
|
||||
* The file to save to
|
||||
* @param system
|
||||
* The system to store
|
||||
* @throws IOException
|
||||
* Indicates a failure to save or encode the system XML.
|
||||
*/
|
||||
public static void saveConfiguredSystem(File file, ParticleSystem system)
|
||||
throws IOException {
|
||||
saveConfiguredSystem(new FileOutputStream(file), system);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a particle system with only ConfigurableEmitters in to an XML file
|
||||
*
|
||||
* @param out
|
||||
* The location to which we'll save
|
||||
* @param system
|
||||
* The system to store
|
||||
* @throws IOException
|
||||
* Indicates a failure to save or encode the system XML.
|
||||
*/
|
||||
public static void saveConfiguredSystem(OutputStream out,
|
||||
ParticleSystem system) throws IOException {
|
||||
try {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder();
|
||||
Document document = builder.newDocument();
|
||||
|
||||
Element root = document.createElement("system");
|
||||
root
|
||||
.setAttribute(
|
||||
"additive",
|
||||
""
|
||||
+ (system.getBlendingMode() == ParticleSystem.BLEND_ADDITIVE));
|
||||
root.setAttribute("points", "" + (system.usePoints()));
|
||||
|
||||
document.appendChild(root);
|
||||
for (int i = 0; i < system.getEmitterCount(); i++) {
|
||||
ParticleEmitter current = system.getEmitter(i);
|
||||
if (current instanceof ConfigurableEmitter) {
|
||||
Element element = emitterToElement(document,
|
||||
(ConfigurableEmitter) current);
|
||||
root.appendChild(element);
|
||||
} else {
|
||||
throw new RuntimeException(
|
||||
"Only ConfigurableEmitter instances can be stored");
|
||||
}
|
||||
}
|
||||
|
||||
Result result = new StreamResult(new OutputStreamWriter(out,
|
||||
"utf-8"));
|
||||
DOMSource source = new DOMSource(document);
|
||||
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
Transformer xformer = factory.newTransformer();
|
||||
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
|
||||
xformer.transform(source, result);
|
||||
} catch (Exception e) {
|
||||
Log.error(e);
|
||||
throw new IOException("Unable to save configured particle system");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single emitter from an XML file
|
||||
*
|
||||
* @param ref
|
||||
* The reference to the emitter XML file to load (classpath or
|
||||
* file)
|
||||
* @return The configured emitter
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ConfigurableEmitter loadEmitter(String ref)
|
||||
throws IOException {
|
||||
return loadEmitter(ResourceLoader.getResourceAsStream(ref), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single emitter from an XML file
|
||||
*
|
||||
* @param ref
|
||||
* The XML file to read
|
||||
* @return The configured emitter
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ConfigurableEmitter loadEmitter(File ref) throws IOException {
|
||||
return loadEmitter(new FileInputStream(ref), null);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single emitter from an XML file
|
||||
*
|
||||
* @param ref
|
||||
* The stream to read the XML from
|
||||
* @return The configured emitter
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ConfigurableEmitter loadEmitter(InputStream ref)
|
||||
throws IOException {
|
||||
return loadEmitter(ref, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single emitter from an XML file
|
||||
*
|
||||
* @param ref
|
||||
* The reference to the emitter XML file to load (classpath or
|
||||
* file)
|
||||
* @return The configured emitter
|
||||
* @param factory
|
||||
* The factory used to create the emitter than will be poulated
|
||||
* with loaded data.
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ConfigurableEmitter loadEmitter(String ref,
|
||||
ConfigurableEmitterFactory factory) throws IOException {
|
||||
return loadEmitter(ResourceLoader.getResourceAsStream(ref), factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single emitter from an XML file
|
||||
*
|
||||
* @param ref
|
||||
* The XML file to read
|
||||
* @return The configured emitter
|
||||
* @param factory
|
||||
* The factory used to create the emitter than will be poulated
|
||||
* with loaded data.
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ConfigurableEmitter loadEmitter(File ref,
|
||||
ConfigurableEmitterFactory factory) throws IOException {
|
||||
return loadEmitter(new FileInputStream(ref), factory);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a single emitter from an XML file
|
||||
*
|
||||
* @param ref
|
||||
* The stream to read the XML from
|
||||
* @param factory
|
||||
* The factory used to create the emitter than will be poulated
|
||||
* with loaded data.
|
||||
* @return The configured emitter
|
||||
* @throws IOException
|
||||
* Indicates a failure to find, read or parse the XML file
|
||||
*/
|
||||
public static ConfigurableEmitter loadEmitter(InputStream ref,
|
||||
ConfigurableEmitterFactory factory) throws IOException {
|
||||
if (factory == null) {
|
||||
factory = new ConfigurableEmitterFactory() {
|
||||
public ConfigurableEmitter createEmitter(String name) {
|
||||
return new ConfigurableEmitter(name);
|
||||
}
|
||||
};
|
||||
}
|
||||
try {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder();
|
||||
Document document = builder.parse(ref);
|
||||
|
||||
if (!document.getDocumentElement().getNodeName().equals("emitter")) {
|
||||
throw new IOException("Not a particle emitter file");
|
||||
}
|
||||
|
||||
ConfigurableEmitter emitter = factory.createEmitter("new");
|
||||
elementToEmitter(document.getDocumentElement(), emitter);
|
||||
|
||||
return emitter;
|
||||
} catch (IOException e) {
|
||||
Log.error(e);
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
Log.error(e);
|
||||
throw new IOException("Unable to load emitter");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a single emitter to the XML file
|
||||
*
|
||||
* @param file
|
||||
* The file to save the emitter to
|
||||
* @param emitter
|
||||
* The emitter to store to the XML file
|
||||
* @throws IOException
|
||||
* Indicates a failure to write or encode the XML
|
||||
*/
|
||||
public static void saveEmitter(File file, ConfigurableEmitter emitter)
|
||||
throws IOException {
|
||||
saveEmitter(new FileOutputStream(file), emitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a single emitter to the XML file
|
||||
*
|
||||
* @param out
|
||||
* The location to which we should save
|
||||
* @param emitter
|
||||
* The emitter to store to the XML file
|
||||
* @throws IOException
|
||||
* Indicates a failure to write or encode the XML
|
||||
*/
|
||||
public static void saveEmitter(OutputStream out, ConfigurableEmitter emitter)
|
||||
throws IOException {
|
||||
try {
|
||||
DocumentBuilder builder = DocumentBuilderFactory.newInstance()
|
||||
.newDocumentBuilder();
|
||||
Document document = builder.newDocument();
|
||||
|
||||
document.appendChild(emitterToElement(document, emitter));
|
||||
Result result = new StreamResult(new OutputStreamWriter(out,
|
||||
"utf-8"));
|
||||
DOMSource source = new DOMSource(document);
|
||||
|
||||
TransformerFactory factory = TransformerFactory.newInstance();
|
||||
Transformer xformer = factory.newTransformer();
|
||||
xformer.setOutputProperty(OutputKeys.INDENT, "yes");
|
||||
|
||||
xformer.transform(source, result);
|
||||
} catch (Exception e) {
|
||||
Log.error(e);
|
||||
throw new IOException("Failed to save emitter");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first child named as specified from the passed XML element
|
||||
*
|
||||
* @param element
|
||||
* The element whose children are interogated
|
||||
* @param name
|
||||
* The name of the element to retrieve
|
||||
* @return The requested element
|
||||
*/
|
||||
private static Element getFirstNamedElement(Element element, String name) {
|
||||
NodeList list = element.getElementsByTagName(name);
|
||||
if (list.getLength() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (Element) list.item(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from an XML element to an configured emitter
|
||||
*
|
||||
* @param element
|
||||
* The XML element to convert
|
||||
* @param emitter
|
||||
* The emitter that will be configured based on the XML
|
||||
*/
|
||||
private static void elementToEmitter(Element element,
|
||||
ConfigurableEmitter emitter) {
|
||||
emitter.name = element.getAttribute("name");
|
||||
emitter.setImageName(element.getAttribute("imageName"));
|
||||
|
||||
String renderType = element.getAttribute("renderType");
|
||||
emitter.usePoints = Particle.INHERIT_POINTS;
|
||||
if (renderType.equals("quads")) {
|
||||
emitter.usePoints = Particle.USE_QUADS;
|
||||
}
|
||||
if (renderType.equals("points")) {
|
||||
emitter.usePoints = Particle.USE_POINTS;
|
||||
}
|
||||
|
||||
String useOriented = element.getAttribute("useOriented");
|
||||
if (useOriented != null)
|
||||
emitter.useOriented = "true".equals(useOriented);
|
||||
|
||||
String useAdditive = element.getAttribute("useAdditive");
|
||||
if (useAdditive != null)
|
||||
emitter.useAdditive = "true".equals(useAdditive);
|
||||
|
||||
parseRangeElement(getFirstNamedElement(element, "spawnInterval"),
|
||||
emitter.spawnInterval);
|
||||
parseRangeElement(getFirstNamedElement(element, "spawnCount"),
|
||||
emitter.spawnCount);
|
||||
parseRangeElement(getFirstNamedElement(element, "initialLife"),
|
||||
emitter.initialLife);
|
||||
parseRangeElement(getFirstNamedElement(element, "initialSize"),
|
||||
emitter.initialSize);
|
||||
parseRangeElement(getFirstNamedElement(element, "xOffset"),
|
||||
emitter.xOffset);
|
||||
parseRangeElement(getFirstNamedElement(element, "yOffset"),
|
||||
emitter.yOffset);
|
||||
parseRangeElement(getFirstNamedElement(element, "initialDistance"),
|
||||
emitter.initialDistance);
|
||||
parseRangeElement(getFirstNamedElement(element, "speed"), emitter.speed);
|
||||
parseRangeElement(getFirstNamedElement(element, "length"),
|
||||
emitter.length);
|
||||
parseRangeElement(getFirstNamedElement(element, "emitCount"),
|
||||
emitter.emitCount);
|
||||
|
||||
parseValueElement(getFirstNamedElement(element, "spread"),
|
||||
emitter.spread);
|
||||
parseValueElement(getFirstNamedElement(element, "angularOffset"),
|
||||
emitter.angularOffset);
|
||||
parseValueElement(getFirstNamedElement(element, "growthFactor"),
|
||||
emitter.growthFactor);
|
||||
parseValueElement(getFirstNamedElement(element, "gravityFactor"),
|
||||
emitter.gravityFactor);
|
||||
parseValueElement(getFirstNamedElement(element, "windFactor"),
|
||||
emitter.windFactor);
|
||||
parseValueElement(getFirstNamedElement(element, "startAlpha"),
|
||||
emitter.startAlpha);
|
||||
parseValueElement(getFirstNamedElement(element, "endAlpha"),
|
||||
emitter.endAlpha);
|
||||
parseValueElement(getFirstNamedElement(element, "alpha"), emitter.alpha);
|
||||
parseValueElement(getFirstNamedElement(element, "size"), emitter.size);
|
||||
parseValueElement(getFirstNamedElement(element, "velocity"),
|
||||
emitter.velocity);
|
||||
parseValueElement(getFirstNamedElement(element, "scaleY"),
|
||||
emitter.scaleY);
|
||||
|
||||
Element color = getFirstNamedElement(element, "color");
|
||||
NodeList steps = color.getElementsByTagName("step");
|
||||
emitter.colors.clear();
|
||||
for (int i = 0; i < steps.getLength(); i++) {
|
||||
Element step = (Element) steps.item(i);
|
||||
float offset = Float.parseFloat(step.getAttribute("offset"));
|
||||
float r = Float.parseFloat(step.getAttribute("r"));
|
||||
float g = Float.parseFloat(step.getAttribute("g"));
|
||||
float b = Float.parseFloat(step.getAttribute("b"));
|
||||
|
||||
emitter.addColorPoint(offset, new Color(r, g, b, 1));
|
||||
}
|
||||
|
||||
// generate new random play length
|
||||
emitter.replay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert from an emitter to a XML element description
|
||||
*
|
||||
* @param document
|
||||
* The document the element will be part of
|
||||
* @param emitter
|
||||
* The emitter to convert
|
||||
* @return The XML element based on the configured emitter
|
||||
*/
|
||||
private static Element emitterToElement(Document document,
|
||||
ConfigurableEmitter emitter) {
|
||||
Element root = document.createElement("emitter");
|
||||
root.setAttribute("name", emitter.name);
|
||||
root.setAttribute("imageName", emitter.imageName == null ? ""
|
||||
: emitter.imageName);
|
||||
root
|
||||
.setAttribute("useOriented", emitter.useOriented ? "true"
|
||||
: "false");
|
||||
root
|
||||
.setAttribute("useAdditive", emitter.useAdditive ? "true"
|
||||
: "false");
|
||||
|
||||
if (emitter.usePoints == Particle.INHERIT_POINTS) {
|
||||
root.setAttribute("renderType", "inherit");
|
||||
}
|
||||
if (emitter.usePoints == Particle.USE_POINTS) {
|
||||
root.setAttribute("renderType", "points");
|
||||
}
|
||||
if (emitter.usePoints == Particle.USE_QUADS) {
|
||||
root.setAttribute("renderType", "quads");
|
||||
}
|
||||
|
||||
root.appendChild(createRangeElement(document, "spawnInterval",
|
||||
emitter.spawnInterval));
|
||||
root.appendChild(createRangeElement(document, "spawnCount",
|
||||
emitter.spawnCount));
|
||||
root.appendChild(createRangeElement(document, "initialLife",
|
||||
emitter.initialLife));
|
||||
root.appendChild(createRangeElement(document, "initialSize",
|
||||
emitter.initialSize));
|
||||
root.appendChild(createRangeElement(document, "xOffset",
|
||||
emitter.xOffset));
|
||||
root.appendChild(createRangeElement(document, "yOffset",
|
||||
emitter.yOffset));
|
||||
root.appendChild(createRangeElement(document, "initialDistance",
|
||||
emitter.initialDistance));
|
||||
root.appendChild(createRangeElement(document, "speed", emitter.speed));
|
||||
root
|
||||
.appendChild(createRangeElement(document, "length",
|
||||
emitter.length));
|
||||
root.appendChild(createRangeElement(document, "emitCount",
|
||||
emitter.emitCount));
|
||||
|
||||
root
|
||||
.appendChild(createValueElement(document, "spread",
|
||||
emitter.spread));
|
||||
root.appendChild(createValueElement(document, "angularOffset",
|
||||
emitter.angularOffset));
|
||||
root.appendChild(createValueElement(document, "growthFactor",
|
||||
emitter.growthFactor));
|
||||
root.appendChild(createValueElement(document, "gravityFactor",
|
||||
emitter.gravityFactor));
|
||||
root.appendChild(createValueElement(document, "windFactor",
|
||||
emitter.windFactor));
|
||||
root.appendChild(createValueElement(document, "startAlpha",
|
||||
emitter.startAlpha));
|
||||
root.appendChild(createValueElement(document, "endAlpha",
|
||||
emitter.endAlpha));
|
||||
root.appendChild(createValueElement(document, "alpha", emitter.alpha));
|
||||
root.appendChild(createValueElement(document, "size", emitter.size));
|
||||
root.appendChild(createValueElement(document, "velocity",
|
||||
emitter.velocity));
|
||||
root
|
||||
.appendChild(createValueElement(document, "scaleY",
|
||||
emitter.scaleY));
|
||||
|
||||
Element color = document.createElement("color");
|
||||
ArrayList list = emitter.colors;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ColorRecord record = (ColorRecord) list.get(i);
|
||||
Element step = document.createElement("step");
|
||||
step.setAttribute("offset", "" + record.pos);
|
||||
step.setAttribute("r", "" + record.col.r);
|
||||
step.setAttribute("g", "" + record.col.g);
|
||||
step.setAttribute("b", "" + record.col.b);
|
||||
|
||||
color.appendChild(step);
|
||||
}
|
||||
|
||||
root.appendChild(color);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an XML element based on a configured range
|
||||
*
|
||||
* @param document
|
||||
* The document the element will be part of
|
||||
* @param name
|
||||
* The name to give the new element
|
||||
* @param range
|
||||
* The configured range
|
||||
* @return A configured XML element on the range
|
||||
*/
|
||||
private static Element createRangeElement(Document document, String name,
|
||||
ConfigurableEmitter.Range range) {
|
||||
Element element = document.createElement(name);
|
||||
element.setAttribute("min", "" + range.getMin());
|
||||
element.setAttribute("max", "" + range.getMax());
|
||||
element.setAttribute("enabled", "" + range.isEnabled());
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an XML element based on a configured value
|
||||
*
|
||||
* @param document
|
||||
* The document the element will be part of
|
||||
* @param name
|
||||
* The name to give the new element
|
||||
* @param value
|
||||
* The configured value
|
||||
* @return A configure XML element based on the value
|
||||
*/
|
||||
private static Element createValueElement(Document document, String name,
|
||||
ConfigurableEmitter.Value value) {
|
||||
Element element = document.createElement(name);
|
||||
|
||||
// void: now writes the value type
|
||||
if (value instanceof SimpleValue) {
|
||||
element.setAttribute("type", "simple");
|
||||
element.setAttribute("value", "" + value.getValue(0));
|
||||
} else if (value instanceof RandomValue) {
|
||||
element.setAttribute("type", "random");
|
||||
element
|
||||
.setAttribute("value", ""
|
||||
+ ((RandomValue) value).getValue());
|
||||
} else if (value instanceof LinearInterpolator) {
|
||||
element.setAttribute("type", "linear");
|
||||
element.setAttribute("min", ""
|
||||
+ ((LinearInterpolator) value).getMin());
|
||||
element.setAttribute("max", ""
|
||||
+ ((LinearInterpolator) value).getMax());
|
||||
element.setAttribute("active", ""
|
||||
+ ((LinearInterpolator) value).isActive());
|
||||
|
||||
ArrayList curve = ((LinearInterpolator) value).getCurve();
|
||||
for (int i = 0; i < curve.size(); i++) {
|
||||
Vector2f point = (Vector2f) curve.get(i);
|
||||
|
||||
Element pointElement = document.createElement("point");
|
||||
pointElement.setAttribute("x", "" + point.x);
|
||||
pointElement.setAttribute("y", "" + point.y);
|
||||
|
||||
element.appendChild(pointElement);
|
||||
}
|
||||
} else {
|
||||
Log.warn("unkown value type ignored: " + value.getClass());
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an XML element into a configured range
|
||||
*
|
||||
* @param element
|
||||
* The XML element to parse
|
||||
* @param range
|
||||
* The range to configure based on the XML
|
||||
*/
|
||||
private static void parseRangeElement(Element element,
|
||||
ConfigurableEmitter.Range range) {
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
range.setMin(Float.parseFloat(element.getAttribute("min")));
|
||||
range.setMax(Float.parseFloat(element.getAttribute("max")));
|
||||
range.setEnabled("true".equals(element.getAttribute("enabled")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an XML element into a configured value
|
||||
*
|
||||
* @param element
|
||||
* The XML element to parse
|
||||
* @param value
|
||||
* The value to configure based on the XML
|
||||
*/
|
||||
private static void parseValueElement(Element element,
|
||||
ConfigurableEmitter.Value value) {
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String type = element.getAttribute("type");
|
||||
String v = element.getAttribute("value");
|
||||
|
||||
if (type == null || type.length() == 0) {
|
||||
// support for old style which did not write the type
|
||||
if (value instanceof SimpleValue) {
|
||||
((SimpleValue) value).setValue(Float.parseFloat(v));
|
||||
} else if (value instanceof RandomValue) {
|
||||
((RandomValue) value).setValue(Float.parseFloat(v));
|
||||
} else {
|
||||
Log.warn("problems reading element, skipping: " + element);
|
||||
}
|
||||
} else {
|
||||
// type given: this is the new style
|
||||
if (type.equals("simple")) {
|
||||
((SimpleValue) value).setValue(Float.parseFloat(v));
|
||||
} else if (type.equals("random")) {
|
||||
((RandomValue) value).setValue(Float.parseFloat(v));
|
||||
} else if (type.equals("linear")) {
|
||||
String min = element.getAttribute("min");
|
||||
String max = element.getAttribute("max");
|
||||
String active = element.getAttribute("active");
|
||||
|
||||
NodeList points = element.getElementsByTagName("point");
|
||||
|
||||
ArrayList curve = new ArrayList();
|
||||
for (int i = 0; i < points.getLength(); i++) {
|
||||
Element point = (Element) points.item(i);
|
||||
|
||||
float x = Float.parseFloat(point.getAttribute("x"));
|
||||
float y = Float.parseFloat(point.getAttribute("y"));
|
||||
|
||||
curve.add(new Vector2f(x, y));
|
||||
}
|
||||
|
||||
((LinearInterpolator) value).setCurve(curve);
|
||||
((LinearInterpolator) value).setMin(Integer.parseInt(min));
|
||||
((LinearInterpolator) value).setMax(Integer.parseInt(max));
|
||||
((LinearInterpolator) value).setActive("true".equals(active));
|
||||
} else {
|
||||
Log.warn("unkown type detected: " + type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
646
lib/slick-source/org/newdawn/slick/particles/ParticleSystem.java
Normal file
646
lib/slick-source/org/newdawn/slick/particles/ParticleSystem.java
Normal file
@@ -0,0 +1,646 @@
|
||||
package org.newdawn.slick.particles;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.newdawn.slick.Color;
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.SlickException;
|
||||
import org.newdawn.slick.opengl.TextureImpl;
|
||||
import org.newdawn.slick.opengl.renderer.Renderer;
|
||||
import org.newdawn.slick.opengl.renderer.SGL;
|
||||
import org.newdawn.slick.util.Log;
|
||||
|
||||
/**
|
||||
* A particle syste responsible for maintaining a set of data about individual
|
||||
* particles which are created and controlled by assigned emitters. This pseudo
|
||||
* chaotic nature hopes to give more organic looking effects
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class ParticleSystem {
|
||||
/** The renderer to use for all GL operations */
|
||||
protected SGL GL = Renderer.get();
|
||||
|
||||
/** The blending mode for the glowy style */
|
||||
public static final int BLEND_ADDITIVE = 1;
|
||||
/** The blending mode for the normal style */
|
||||
public static final int BLEND_COMBINE = 2;
|
||||
|
||||
/** The default number of particles in the system */
|
||||
private static final int DEFAULT_PARTICLES = 100;
|
||||
|
||||
/** List of emitters to be removed */
|
||||
private ArrayList removeMe = new ArrayList();
|
||||
|
||||
/**
|
||||
* Set the path from which images should be loaded
|
||||
*
|
||||
* @param path
|
||||
* The path from which images should be loaded
|
||||
*/
|
||||
public static void setRelativePath(String path) {
|
||||
ConfigurableEmitter.setRelativePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* A pool of particles being used by a specific emitter
|
||||
*
|
||||
* @author void
|
||||
*/
|
||||
private class ParticlePool
|
||||
{
|
||||
/** The particles being rendered and maintained */
|
||||
public Particle[] particles;
|
||||
/** The list of particles left to be used, if this size() == 0 then the particle engine was too small for the effect */
|
||||
public ArrayList available;
|
||||
|
||||
/**
|
||||
* Create a new particle pool contiaining a set of particles
|
||||
*
|
||||
* @param system The system that owns the particles over all
|
||||
* @param maxParticles The maximum number of particles in the pool
|
||||
*/
|
||||
public ParticlePool( ParticleSystem system, int maxParticles )
|
||||
{
|
||||
particles = new Particle[ maxParticles ];
|
||||
available = new ArrayList();
|
||||
|
||||
for( int i=0; i<particles.length; i++ )
|
||||
{
|
||||
particles[i] = createParticle( system );
|
||||
}
|
||||
|
||||
reset(system);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rest the list of particles
|
||||
*
|
||||
* @param system The system in which the particle belong
|
||||
*/
|
||||
public void reset(ParticleSystem system) {
|
||||
available.clear();
|
||||
|
||||
for( int i=0; i<particles.length; i++ )
|
||||
{
|
||||
available.add(particles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A map from emitter to a the particle pool holding the particles it uses
|
||||
* void: this is now sorted by emitters to allow emitter specfic state to be set for
|
||||
* each emitter. actually this is used to allow setting an individual blend mode for
|
||||
* each emitter
|
||||
*/
|
||||
protected HashMap particlesByEmitter = new HashMap();
|
||||
/** The maximum number of particles allows per emitter */
|
||||
protected int maxParticlesPerEmitter;
|
||||
|
||||
/** The list of emittered producing and controlling particles */
|
||||
protected ArrayList emitters = new ArrayList();
|
||||
|
||||
/** The dummy particle to return should no more particles be available */
|
||||
protected Particle dummy;
|
||||
/** The blending mode */
|
||||
private int blendingMode = BLEND_COMBINE;
|
||||
/** The number of particles in use */
|
||||
private int pCount;
|
||||
/** True if we're going to use points to render the particles */
|
||||
private boolean usePoints;
|
||||
/** The x coordinate at which this system should be rendered */
|
||||
private float x;
|
||||
/** The x coordinate at which this system should be rendered */
|
||||
private float y;
|
||||
/** True if we should remove completed emitters */
|
||||
private boolean removeCompletedEmitters = true;
|
||||
|
||||
/** The default image for the particles */
|
||||
private Image sprite;
|
||||
/** True if the particle system is visible */
|
||||
private boolean visible = true;
|
||||
/** The name of the default image */
|
||||
private String defaultImageName;
|
||||
/** The mask used to make the particle image background transparent if any */
|
||||
private Color mask;
|
||||
|
||||
/**
|
||||
* Create a new particle system
|
||||
*
|
||||
* @param defaultSprite The sprite to render for each particle
|
||||
*/
|
||||
public ParticleSystem(Image defaultSprite) {
|
||||
this(defaultSprite, DEFAULT_PARTICLES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new particle system
|
||||
*
|
||||
* @param defaultSpriteRef The sprite to render for each particle
|
||||
*/
|
||||
public ParticleSystem(String defaultSpriteRef) {
|
||||
this(defaultSpriteRef, DEFAULT_PARTICLES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the state of the system
|
||||
*/
|
||||
public void reset() {
|
||||
Iterator pools = particlesByEmitter.values().iterator();
|
||||
while (pools.hasNext()) {
|
||||
ParticlePool pool = (ParticlePool) pools.next();
|
||||
pool.reset(this);
|
||||
}
|
||||
|
||||
for (int i=0;i<emitters.size();i++) {
|
||||
ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);
|
||||
emitter.resetState();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this system is currently visible, i.e. it's actually
|
||||
* rendered
|
||||
*
|
||||
* @return True if the particle system is rendered
|
||||
*/
|
||||
public boolean isVisible() {
|
||||
return visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate whether the particle system should be visible, i.e. whether
|
||||
* it'll actually render
|
||||
*
|
||||
* @param visible True if the particle system should render
|
||||
*/
|
||||
public void setVisible(boolean visible) {
|
||||
this.visible = visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if completed emitters should be removed
|
||||
*
|
||||
* @param remove True if completed emitters should be removed
|
||||
*/
|
||||
public void setRemoveCompletedEmitters(boolean remove) {
|
||||
removeCompletedEmitters = remove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicate if this engine should use points to render the particles
|
||||
*
|
||||
* @param usePoints True if points should be used to render the particles
|
||||
*/
|
||||
public void setUsePoints(boolean usePoints) {
|
||||
this.usePoints = usePoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this engine should use points to render the particles
|
||||
*
|
||||
* @return True if the engine should use points to render the particles
|
||||
*/
|
||||
public boolean usePoints() {
|
||||
return usePoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new particle system
|
||||
*
|
||||
* @param defaultSpriteRef The sprite to render for each particle
|
||||
* @param maxParticles The number of particles available in the system
|
||||
*/
|
||||
public ParticleSystem(String defaultSpriteRef, int maxParticles) {
|
||||
this(defaultSpriteRef, maxParticles, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new particle system
|
||||
*
|
||||
* @param defaultSpriteRef The sprite to render for each particle
|
||||
* @param maxParticles The number of particles available in the system
|
||||
* @param mask The mask used to make the sprite image transparent
|
||||
*/
|
||||
public ParticleSystem(String defaultSpriteRef, int maxParticles, Color mask) {
|
||||
this.maxParticlesPerEmitter= maxParticles;
|
||||
this.mask = mask;
|
||||
|
||||
setDefaultImageName(defaultSpriteRef);
|
||||
dummy = createParticle(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new particle system
|
||||
*
|
||||
* @param defaultSprite The sprite to render for each particle
|
||||
* @param maxParticles The number of particles available in the system
|
||||
*/
|
||||
public ParticleSystem(Image defaultSprite, int maxParticles) {
|
||||
this.maxParticlesPerEmitter= maxParticles;
|
||||
|
||||
sprite = defaultSprite;
|
||||
dummy = createParticle(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default image name
|
||||
*
|
||||
* @param ref The default image name
|
||||
*/
|
||||
public void setDefaultImageName(String ref) {
|
||||
defaultImageName = ref;
|
||||
sprite = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blending mode in use
|
||||
*
|
||||
* @see #BLEND_COMBINE
|
||||
* @see #BLEND_ADDITIVE
|
||||
* @return The blending mode in use
|
||||
*/
|
||||
public int getBlendingMode() {
|
||||
return blendingMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a particle specific to this system, override for your own implementations.
|
||||
* These particles will be cached and reused within this system.
|
||||
*
|
||||
* @param system The system owning this particle
|
||||
* @return The newly created particle.
|
||||
*/
|
||||
protected Particle createParticle(ParticleSystem system) {
|
||||
return new Particle(system);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the blending mode for the particles
|
||||
*
|
||||
* @param mode The mode for blending particles together
|
||||
*/
|
||||
public void setBlendingMode(int mode) {
|
||||
this.blendingMode = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of emitters applied to the system
|
||||
*
|
||||
* @return The number of emitters applied to the system
|
||||
*/
|
||||
public int getEmitterCount() {
|
||||
return emitters.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an emitter a specified index int he list contained within this system
|
||||
*
|
||||
* @param index The index of the emitter to retrieve
|
||||
* @return The particle emitter
|
||||
*/
|
||||
public ParticleEmitter getEmitter(int index) {
|
||||
return (ParticleEmitter) emitters.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a particle emitter to be used on this system
|
||||
*
|
||||
* @param emitter The emitter to be added
|
||||
*/
|
||||
public void addEmitter(ParticleEmitter emitter) {
|
||||
emitters.add(emitter);
|
||||
|
||||
ParticlePool pool= new ParticlePool( this, maxParticlesPerEmitter );
|
||||
particlesByEmitter.put( emitter, pool );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a particle emitter that is currently used in the system
|
||||
*
|
||||
* @param emitter The emitter to be removed
|
||||
*/
|
||||
public void removeEmitter(ParticleEmitter emitter) {
|
||||
emitters.remove(emitter);
|
||||
particlesByEmitter.remove(emitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all the emitters from the system
|
||||
*/
|
||||
public void removeAllEmitters() {
|
||||
for (int i=0;i<emitters.size();i++) {
|
||||
removeEmitter((ParticleEmitter) emitters.get(i));
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the x coordiante of the position of the system
|
||||
*
|
||||
* @return The x coordinate of the position of the system
|
||||
*/
|
||||
public float getPositionX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the y coordiante of the position of the system
|
||||
*
|
||||
* @return The y coordinate of the position of the system
|
||||
*/
|
||||
public float getPositionY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position at which this system should render relative to the current
|
||||
* graphics context setup
|
||||
*
|
||||
* @param x The x coordinate at which this system should be centered
|
||||
* @param y The y coordinate at which this system should be centered
|
||||
*/
|
||||
public void setPosition(float x, float y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the particles in the system
|
||||
*/
|
||||
public void render() {
|
||||
render(x,y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the particles in the system
|
||||
*
|
||||
* @param x The x coordinate to render the particle system at (in the current coordinate space)
|
||||
* @param y The y coordinate to render the particle system at (in the current coordiante space)
|
||||
*/
|
||||
public void render(float x, float y) {
|
||||
if ((sprite == null) && (defaultImageName != null)) {
|
||||
loadSystemParticleImage();
|
||||
}
|
||||
|
||||
if (!visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
GL.glTranslatef(x,y,0);
|
||||
|
||||
if (blendingMode == BLEND_ADDITIVE) {
|
||||
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);
|
||||
}
|
||||
if (usePoints()) {
|
||||
GL.glEnable( SGL.GL_POINT_SMOOTH );
|
||||
TextureImpl.bindNone();
|
||||
}
|
||||
|
||||
// iterate over all emitters
|
||||
for( int emitterIdx=0; emitterIdx<emitters.size(); emitterIdx++ )
|
||||
{
|
||||
// get emitter
|
||||
ParticleEmitter emitter = (ParticleEmitter) emitters.get(emitterIdx);
|
||||
|
||||
if (!emitter.isEnabled()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for additive override and enable when set
|
||||
if (emitter.useAdditive()) {
|
||||
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);
|
||||
}
|
||||
|
||||
// now get the particle pool for this emitter and render all particles that are in use
|
||||
ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
|
||||
Image image = emitter.getImage();
|
||||
if (image == null) {
|
||||
image = this.sprite;
|
||||
}
|
||||
|
||||
if (!emitter.isOriented() && !emitter.usePoints(this)) {
|
||||
image.startUse();
|
||||
}
|
||||
|
||||
for (int i = 0; i < pool.particles.length; i++)
|
||||
{
|
||||
if (pool.particles[i].inUse())
|
||||
pool.particles[i].render();
|
||||
}
|
||||
|
||||
if (!emitter.isOriented() && !emitter.usePoints(this)) {
|
||||
image.endUse();
|
||||
}
|
||||
|
||||
// reset additive blend mode
|
||||
if (emitter.useAdditive()) {
|
||||
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
}
|
||||
|
||||
if (usePoints()) {
|
||||
GL.glDisable( SGL.GL_POINT_SMOOTH );
|
||||
}
|
||||
if (blendingMode == BLEND_ADDITIVE) {
|
||||
GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);
|
||||
}
|
||||
|
||||
Color.white.bind();
|
||||
GL.glTranslatef(-x,-y,0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the system particle image as the extension permissions
|
||||
*/
|
||||
private void loadSystemParticleImage() {
|
||||
AccessController.doPrivileged(new PrivilegedAction() {
|
||||
public Object run() {
|
||||
try {
|
||||
if (mask != null) {
|
||||
sprite = new Image(defaultImageName, mask);
|
||||
} else {
|
||||
sprite = new Image(defaultImageName);
|
||||
}
|
||||
} catch (SlickException e) {
|
||||
Log.error(e);
|
||||
defaultImageName = null;
|
||||
}
|
||||
return null; // nothing to return
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the system, request the assigned emitters update the particles
|
||||
*
|
||||
* @param delta The amount of time thats passed since last update in milliseconds
|
||||
*/
|
||||
public void update(int delta) {
|
||||
if ((sprite == null) && (defaultImageName != null)) {
|
||||
loadSystemParticleImage();
|
||||
}
|
||||
|
||||
removeMe.clear();
|
||||
ArrayList emitters = new ArrayList(this.emitters);
|
||||
for (int i=0;i<emitters.size();i++) {
|
||||
ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);
|
||||
if (emitter.isEnabled()) {
|
||||
emitter.update(this, delta);
|
||||
if (removeCompletedEmitters) {
|
||||
if (emitter.completed()) {
|
||||
removeMe.add(emitter);
|
||||
particlesByEmitter.remove(emitter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
this.emitters.removeAll(removeMe);
|
||||
|
||||
pCount = 0;
|
||||
|
||||
if (!particlesByEmitter.isEmpty())
|
||||
{
|
||||
Iterator it= particlesByEmitter.keySet().iterator();
|
||||
while (it.hasNext())
|
||||
{
|
||||
ParticleEmitter emitter = (ParticleEmitter) it.next();
|
||||
if (emitter.isEnabled()) {
|
||||
ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
|
||||
for (int i=0;i<pool.particles.length;i++) {
|
||||
if (pool.particles[i].life > 0) {
|
||||
pool.particles[i].update(delta);
|
||||
pCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of particles in use in this system
|
||||
*
|
||||
* @return The number of particles in use in this system
|
||||
*/
|
||||
public int getParticleCount() {
|
||||
return pCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a new particle from the system. This should be used by emitters to
|
||||
* request particles
|
||||
*
|
||||
* @param emitter The emitter requesting the particle
|
||||
* @param life The time the new particle should live for
|
||||
* @return A particle from the system
|
||||
*/
|
||||
public Particle getNewParticle(ParticleEmitter emitter, float life)
|
||||
{
|
||||
ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
|
||||
ArrayList available = pool.available;
|
||||
if (available.size() > 0)
|
||||
{
|
||||
Particle p = (Particle) available.remove(available.size()-1);
|
||||
p.init(emitter, life);
|
||||
p.setImage(sprite);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
Log.warn("Ran out of particles (increase the limit)!");
|
||||
return dummy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Release a particle back to the system once it has expired
|
||||
*
|
||||
* @param particle The particle to be released
|
||||
*/
|
||||
public void release(Particle particle) {
|
||||
if (particle != dummy)
|
||||
{
|
||||
ParticlePool pool = (ParticlePool)particlesByEmitter.get( particle.getEmitter() );
|
||||
pool.available.add(particle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all the particles owned by the specified emitter
|
||||
*
|
||||
* @param emitter The emitter owning the particles that should be released
|
||||
*/
|
||||
public void releaseAll(ParticleEmitter emitter) {
|
||||
if( !particlesByEmitter.isEmpty() )
|
||||
{
|
||||
Iterator it= particlesByEmitter.values().iterator();
|
||||
while( it.hasNext())
|
||||
{
|
||||
ParticlePool pool= (ParticlePool)it.next();
|
||||
for (int i=0;i<pool.particles.length;i++) {
|
||||
if (pool.particles[i].inUse()) {
|
||||
if (pool.particles[i].getEmitter() == emitter) {
|
||||
pool.particles[i].setLife(-1);
|
||||
release(pool.particles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all the particles owned by the specified emitter
|
||||
*
|
||||
* @param emitter The emitter owning the particles that should be released
|
||||
* @param x The amount on the x axis to move the particles
|
||||
* @param y The amount on the y axis to move the particles
|
||||
*/
|
||||
public void moveAll(ParticleEmitter emitter, float x, float y) {
|
||||
ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);
|
||||
for (int i=0;i<pool.particles.length;i++) {
|
||||
if (pool.particles[i].inUse()) {
|
||||
pool.particles[i].move(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a duplicate of this system. This would have been nicer as a different interface
|
||||
* but may cause to much API change headache. Maybe next full version release it should be
|
||||
* rethought.
|
||||
*
|
||||
* TODO: Consider refactor at next point release
|
||||
*
|
||||
* @return A copy of this particle system
|
||||
* @throws SlickException Indicates a failure during copy or a invalid particle system to be duplicated
|
||||
*/
|
||||
public ParticleSystem duplicate() throws SlickException {
|
||||
for (int i=0;i<emitters.size();i++) {
|
||||
if (!(emitters.get(i) instanceof ConfigurableEmitter)) {
|
||||
throw new SlickException("Only systems contianing configurable emitters can be duplicated");
|
||||
}
|
||||
}
|
||||
|
||||
ParticleSystem theCopy = null;
|
||||
try {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
ParticleIO.saveConfiguredSystem(bout, this);
|
||||
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
|
||||
theCopy = ParticleIO.loadConfiguredSystem(bin);
|
||||
} catch (IOException e) {
|
||||
Log.error("Failed to duplicate particle system");
|
||||
throw new SlickException("Unable to duplicated particle system", e);
|
||||
}
|
||||
|
||||
return theCopy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package org.newdawn.slick.particles.effects;
|
||||
|
||||
import org.newdawn.slick.Image;
|
||||
import org.newdawn.slick.particles.Particle;
|
||||
import org.newdawn.slick.particles.ParticleEmitter;
|
||||
import org.newdawn.slick.particles.ParticleSystem;
|
||||
|
||||
/**
|
||||
* A stock effect for fire usin the particle system
|
||||
*
|
||||
* @author kevin
|
||||
*/
|
||||
public class FireEmitter implements ParticleEmitter {
|
||||
/** The x coordinate of the center of the fire effect */
|
||||
private int x;
|
||||
/** The y coordinate of the center of the fire effect */
|
||||
private int y;
|
||||
|
||||
/** The particle emission rate */
|
||||
private int interval = 50;
|
||||
/** Time til the next particle */
|
||||
private int timer;
|
||||
/** The size of the initial particles */
|
||||
private float size = 40;
|
||||
|
||||
/**
|
||||
* Create a default fire effect at 0,0
|
||||
*/
|
||||
public FireEmitter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default fire effect at x,y
|
||||
*
|
||||
* @param x The x coordinate of the fire effect
|
||||
* @param y The y coordinate of the fire effect
|
||||
*/
|
||||
public FireEmitter(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default fire effect at x,y
|
||||
*
|
||||
* @param x The x coordinate of the fire effect
|
||||
* @param y The y coordinate of the fire effect
|
||||
* @param size The size of the particle being pumped out
|
||||
*/
|
||||
public FireEmitter(int x, int y, float size) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#update(org.newdawn.slick.particles.ParticleSystem, int)
|
||||
*/
|
||||
public void update(ParticleSystem system, int delta) {
|
||||
timer -= delta;
|
||||
if (timer <= 0) {
|
||||
timer = interval;
|
||||
Particle p = system.getNewParticle(this, 1000);
|
||||
p.setColor(1, 1, 1, 0.5f);
|
||||
p.setPosition(x, y);
|
||||
p.setSize(size);
|
||||
float vx = (float) (-0.02f + (Math.random() * 0.04f));
|
||||
float vy = (float) (-(Math.random() * 0.15f));
|
||||
p.setVelocity(vx,vy,1.1f);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#updateParticle(org.newdawn.slick.particles.Particle, int)
|
||||
*/
|
||||
public void updateParticle(Particle particle, int delta) {
|
||||
if (particle.getLife() > 600) {
|
||||
particle.adjustSize(0.07f * delta);
|
||||
} else {
|
||||
particle.adjustSize(-0.04f * delta * (size / 40.0f));
|
||||
}
|
||||
float c = 0.002f * delta;
|
||||
particle.adjustColor(0,-c/2,-c*2,-c/4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#isEnabled()
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#setEnabled(boolean)
|
||||
*/
|
||||
public void setEnabled(boolean enabled) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#completed()
|
||||
*/
|
||||
public boolean completed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#useAdditive()
|
||||
*/
|
||||
public boolean useAdditive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#getImage()
|
||||
*/
|
||||
public Image getImage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#usePoints(org.newdawn.slick.particles.ParticleSystem)
|
||||
*/
|
||||
public boolean usePoints(ParticleSystem system) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#isOriented()
|
||||
*/
|
||||
public boolean isOriented() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#wrapUp()
|
||||
*/
|
||||
public void wrapUp() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see org.newdawn.slick.particles.ParticleEmitter#resetState()
|
||||
*/
|
||||
public void resetState() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<BODY>
|
||||
This package should contain stock effects for simple particle systems.
|
||||
</BODY>
|
||||
@@ -0,0 +1,4 @@
|
||||
<BODY>
|
||||
The particle engine maintains a set of small sprites being controlled
|
||||
by emitters to give some special effect.
|
||||
</BODY>
|
||||
Reference in New Issue
Block a user