Path: blob/master/SLICK_HOME/src/org/newdawn/slick/particles/ParticleSystem.java
1457 views
package org.newdawn.slick.particles;12import java.io.ByteArrayInputStream;3import java.io.ByteArrayOutputStream;4import java.io.IOException;5import java.security.AccessController;6import java.security.PrivilegedAction;7import java.util.ArrayList;8import java.util.HashMap;9import java.util.Iterator;1011import org.newdawn.slick.Color;12import org.newdawn.slick.Image;13import org.newdawn.slick.SlickException;14import org.newdawn.slick.opengl.TextureImpl;15import org.newdawn.slick.opengl.renderer.Renderer;16import org.newdawn.slick.opengl.renderer.SGL;17import org.newdawn.slick.util.Log;1819/**20* A particle syste responsible for maintaining a set of data about individual21* particles which are created and controlled by assigned emitters. This pseudo22* chaotic nature hopes to give more organic looking effects23*24* @author kevin25*/26public class ParticleSystem {27/** The renderer to use for all GL operations */28protected static SGL GL = Renderer.get();2930/** The blending mode for the glowy style */31public static final int BLEND_ADDITIVE = 1;32/** The blending mode for the normal style */33public static final int BLEND_COMBINE = 2;3435/** The default number of particles in the system */36private static final int DEFAULT_PARTICLES = 100;3738/**39* Set the path from which images should be loaded40*41* @param path42* The path from which images should be loaded43*/44public static void setRelativePath(String path) {45ConfigurableEmitter.setRelativePath(path);46}4748/**49* A pool of particles being used by a specific emitter50*51* @author void52*/53private class ParticlePool54{55/** The particles being rendered and maintained */56public Particle[] particles;57/** The list of particles left to be used, if this size() == 0 then the particle engine was too small for the effect */58public ArrayList available;5960/**61* Create a new particle pool contiaining a set of particles62*63* @param system The system that owns the particles over all64* @param maxParticles The maximum number of particles in the pool65*/66public ParticlePool( ParticleSystem system, int maxParticles )67{68particles = new Particle[ maxParticles ];69available = new ArrayList();7071for( int i=0; i<particles.length; i++ )72{73particles[i] = createParticle( system );74}7576reset(system);77}7879/**80* Rest the list of particles81*82* @param system The system in which the particle belong83*/84public void reset(ParticleSystem system) {85available.clear();8687for( int i=0; i<particles.length; i++ )88{89available.add(particles[i]);90}91}92}9394/**95* A map from emitter to a the particle pool holding the particles it uses96* void: this is now sorted by emitters to allow emitter specfic state to be set for97* each emitter. actually this is used to allow setting an individual blend mode for98* each emitter99*/100protected HashMap particlesByEmitter = new HashMap();101/** The maximum number of particles allows per emitter */102protected int maxParticlesPerEmitter;103104/** The list of emittered producing and controlling particles */105protected ArrayList emitters = new ArrayList();106107/** The dummy particle to return should no more particles be available */108protected Particle dummy;109/** The blending mode */110private int blendingMode = BLEND_COMBINE;111/** The number of particles in use */112private int pCount;113/** True if we're going to use points to render the particles */114private boolean usePoints;115/** The x coordinate at which this system should be rendered */116private float x;117/** The x coordinate at which this system should be rendered */118private float y;119/** True if we should remove completed emitters */120private boolean removeCompletedEmitters = true;121122/** The default image for the particles */123private Image sprite;124/** True if the particle system is visible */125private boolean visible = true;126/** The name of the default image */127private String defaultImageName;128/** The mask used to make the particle image background transparent if any */129private Color mask;130131/**132* Create a new particle system133*134* @param defaultSprite The sprite to render for each particle135*/136public ParticleSystem(Image defaultSprite) {137this(defaultSprite, DEFAULT_PARTICLES);138}139140/**141* Create a new particle system142*143* @param defaultSpriteRef The sprite to render for each particle144*/145public ParticleSystem(String defaultSpriteRef) {146this(defaultSpriteRef, DEFAULT_PARTICLES);147}148149/**150* Reset the state of the system151*/152public void reset() {153Iterator pools = particlesByEmitter.values().iterator();154while (pools.hasNext()) {155ParticlePool pool = (ParticlePool) pools.next();156pool.reset(this);157}158159for (int i=0;i<emitters.size();i++) {160ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);161emitter.resetState();162}163}164165/**166* Check if this system is currently visible, i.e. it's actually167* rendered168*169* @return True if the particle system is rendered170*/171public boolean isVisible() {172return visible;173}174175/**176* Indicate whether the particle system should be visible, i.e. whether177* it'll actually render178*179* @param visible True if the particle system should render180*/181public void setVisible(boolean visible) {182this.visible = visible;183}184185/**186* Indicate if completed emitters should be removed187*188* @param remove True if completed emitters should be removed189*/190public void setRemoveCompletedEmitters(boolean remove) {191removeCompletedEmitters = remove;192}193194/**195* Indicate if this engine should use points to render the particles196*197* @param usePoints True if points should be used to render the particles198*/199public void setUsePoints(boolean usePoints) {200this.usePoints = usePoints;201}202203/**204* Check if this engine should use points to render the particles205*206* @return True if the engine should use points to render the particles207*/208public boolean usePoints() {209return usePoints;210}211212/**213* Create a new particle system214*215* @param defaultSpriteRef The sprite to render for each particle216* @param maxParticles The number of particles available in the system217*/218public ParticleSystem(String defaultSpriteRef, int maxParticles) {219this(defaultSpriteRef, maxParticles, null);220}221222/**223* Create a new particle system224*225* @param defaultSpriteRef The sprite to render for each particle226* @param maxParticles The number of particles available in the system227* @param mask The mask used to make the sprite image transparent228*/229public ParticleSystem(String defaultSpriteRef, int maxParticles, Color mask) {230this.maxParticlesPerEmitter= maxParticles;231this.mask = mask;232233setDefaultImageName(defaultSpriteRef);234dummy = createParticle(this);235}236237/**238* Create a new particle system239*240* @param defaultSprite The sprite to render for each particle241* @param maxParticles The number of particles available in the system242*/243public ParticleSystem(Image defaultSprite, int maxParticles) {244this.maxParticlesPerEmitter= maxParticles;245246sprite = defaultSprite;247dummy = createParticle(this);248}249250/**251* Set the default image name252*253* @param ref The default image name254*/255public void setDefaultImageName(String ref) {256defaultImageName = ref;257sprite = null;258}259260/**261* Get the blending mode in use262*263* @see #BLEND_COMBINE264* @see #BLEND_ADDITIVE265* @return The blending mode in use266*/267public int getBlendingMode() {268return blendingMode;269}270271/**272* Create a particle specific to this system, override for your own implementations.273* These particles will be cached and reused within this system.274*275* @param system The system owning this particle276* @return The newly created particle.277*/278protected Particle createParticle(ParticleSystem system) {279return new Particle(system);280}281282/**283* Set the blending mode for the particles284*285* @param mode The mode for blending particles together286*/287public void setBlendingMode(int mode) {288this.blendingMode = mode;289}290291/**292* Get the number of emitters applied to the system293*294* @return The number of emitters applied to the system295*/296public int getEmitterCount() {297return emitters.size();298}299300/**301* Get an emitter a specified index int he list contained within this system302*303* @param index The index of the emitter to retrieve304* @return The particle emitter305*/306public ParticleEmitter getEmitter(int index) {307return (ParticleEmitter) emitters.get(index);308}309310/**311* Add a particle emitter to be used on this system312*313* @param emitter The emitter to be added314*/315public void addEmitter(ParticleEmitter emitter) {316emitters.add(emitter);317318ParticlePool pool= new ParticlePool( this, maxParticlesPerEmitter );319particlesByEmitter.put( emitter, pool );320}321322/**323* Remove a particle emitter that is currently used in the system324*325* @param emitter The emitter to be removed326*/327public void removeEmitter(ParticleEmitter emitter) {328emitters.remove(emitter);329particlesByEmitter.remove( emitter );330}331332/**333* Remove all the emitters from the system334*/335public void removeAllEmitters() {336for (int i=0;i<emitters.size();i++) {337removeEmitter((ParticleEmitter) emitters.get(i));338i--;339}340}341342/**343* Get the x coordiante of the position of the system344*345* @return The x coordinate of the position of the system346*/347public float getPositionX() {348return x;349}350351/**352* Get the y coordiante of the position of the system353*354* @return The y coordinate of the position of the system355*/356public float getPositionY() {357return y;358}359360/**361* Set the position at which this system should render relative to the current362* graphics context setup363*364* @param x The x coordinate at which this system should be centered365* @param y The y coordinate at which this system should be centered366*/367public void setPosition(float x, float y) {368this.x = x;369this.y = y;370}371372/**373* Render the particles in the system374*/375public void render() {376render(x,y);377}378379/**380* Render the particles in the system381*382* @param x The x coordinate to render the particle system at (in the current coordinate space)383* @param y The y coordinate to render the particle system at (in the current coordiante space)384*/385public void render(float x, float y) {386if ((sprite == null) && (defaultImageName != null)) {387loadSystemParticleImage();388}389390if (!visible) {391return;392}393394GL.glTranslatef(x,y,0);395396if (blendingMode == BLEND_ADDITIVE) {397GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);398}399if (usePoints()) {400GL.glEnable( SGL.GL_POINT_SMOOTH );401TextureImpl.bindNone();402}403404// iterate over all emitters405for( int emitterIdx=0; emitterIdx<emitters.size(); emitterIdx++ )406{407// get emitter408ParticleEmitter emitter = (ParticleEmitter) emitters.get(emitterIdx);409410// check for additive override and enable when set411if (emitter.useAdditive()) {412GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE);413}414415// now get the particle pool for this emitter and render all particles that are in use416ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);417Image image = emitter.getImage();418if (image == null) {419image = this.sprite;420}421422if (!emitter.isOriented() && !emitter.usePoints(this)) {423image.startUse();424}425426for (int i = 0; i < pool.particles.length; i++)427{428if (pool.particles[i].inUse())429pool.particles[i].render();430}431432if (!emitter.isOriented() && !emitter.usePoints(this)) {433image.endUse();434}435436// reset additive blend mode437if (emitter.useAdditive()) {438GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);439}440}441442if (usePoints()) {443GL.glDisable( SGL.GL_POINT_SMOOTH );444}445if (blendingMode == BLEND_ADDITIVE) {446GL.glBlendFunc(SGL.GL_SRC_ALPHA, SGL.GL_ONE_MINUS_SRC_ALPHA);447}448449Color.white.bind();450GL.glTranslatef(-x,-y,0);451}452453/**454* Load the system particle image as the extension permissions455*/456private void loadSystemParticleImage() {457AccessController.doPrivileged(new PrivilegedAction() {458public Object run() {459try {460if (mask != null) {461sprite = new Image(defaultImageName, mask);462} else {463sprite = new Image(defaultImageName);464}465} catch (SlickException e) {466Log.error(e);467defaultImageName = null;468}469return null; // nothing to return470}471});472}473474/**475* Update the system, request the assigned emitters update the particles476*477* @param delta The amount of time thats passed since last update in milliseconds478*/479public void update(int delta) {480if ((sprite == null) && (defaultImageName != null)) {481loadSystemParticleImage();482}483484ArrayList removeMe = new ArrayList();485for (int i=0;i<emitters.size();i++) {486ParticleEmitter emitter = (ParticleEmitter) emitters.get(i);487if (emitter.isEnabled()) {488emitter.update(this, delta);489if (removeCompletedEmitters) {490if (emitter.completed()) {491removeMe.add(emitter);492particlesByEmitter.remove(emitter);493}494}495}496}497emitters.removeAll(removeMe);498499pCount = 0;500501if (!particlesByEmitter.isEmpty())502{503Iterator it= particlesByEmitter.values().iterator();504while (it.hasNext())505{506ParticlePool pool= (ParticlePool)it.next();507for (int i=0;i<pool.particles.length;i++) {508if (pool.particles[i].life > 0) {509pool.particles[i].update(delta);510pCount++;511}512}513}514}515}516517/**518* Get the number of particles in use in this system519*520* @return The number of particles in use in this system521*/522public int getParticleCount() {523return pCount;524}525526/**527* Get a new particle from the system. This should be used by emitters to528* request particles529*530* @param emitter The emitter requesting the particle531* @param life The time the new particle should live for532* @return A particle from the system533*/534public Particle getNewParticle(ParticleEmitter emitter, float life)535{536ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);537ArrayList available = pool.available;538if (available.size() > 0)539{540Particle p = (Particle) available.remove(available.size()-1);541p.init(emitter, life);542p.setImage(sprite);543544return p;545}546547Log.warn("Ran out of particles (increase the limit)!");548return dummy;549}550551/**552* Release a particle back to the system once it has expired553*554* @param particle The particle to be released555*/556public void release(Particle particle) {557if (particle != dummy)558{559ParticlePool pool = (ParticlePool)particlesByEmitter.get( particle.getEmitter() );560pool.available.add(particle);561}562}563564/**565* Release all the particles owned by the specified emitter566*567* @param emitter The emitter owning the particles that should be released568*/569public void releaseAll(ParticleEmitter emitter) {570if( !particlesByEmitter.isEmpty() )571{572Iterator it= particlesByEmitter.values().iterator();573while( it.hasNext())574{575ParticlePool pool= (ParticlePool)it.next();576for (int i=0;i<pool.particles.length;i++) {577if (pool.particles[i].inUse()) {578if (pool.particles[i].getEmitter() == emitter) {579pool.particles[i].setLife(-1);580release(pool.particles[i]);581}582}583}584}585}586}587588/**589* Move all the particles owned by the specified emitter590*591* @param emitter The emitter owning the particles that should be released592* @param x The amount on the x axis to move the particles593* @param y The amount on the y axis to move the particles594*/595public void moveAll(ParticleEmitter emitter, float x, float y) {596ParticlePool pool = (ParticlePool) particlesByEmitter.get(emitter);597for (int i=0;i<pool.particles.length;i++) {598if (pool.particles[i].inUse()) {599pool.particles[i].move(x, y);600}601}602}603604/**605* Create a duplicate of this system. This would have been nicer as a different interface606* but may cause to much API change headache. Maybe next full version release it should be607* rethought.608*609* TODO: Consider refactor at next point release610*611* @return A copy of this particle system612* @throws SlickException Indicates a failure during copy or a invalid particle system to be duplicated613*/614public ParticleSystem duplicate() throws SlickException {615for (int i=0;i<emitters.size();i++) {616if (!(emitters.get(i) instanceof ConfigurableEmitter)) {617throw new SlickException("Only systems contianing configurable emitters can be duplicated");618}619}620621ParticleSystem theCopy = null;622try {623ByteArrayOutputStream bout = new ByteArrayOutputStream();624ParticleIO.saveConfiguredSystem(bout, this);625ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());626theCopy = ParticleIO.loadConfiguredSystem(bin);627} catch (IOException e) {628Log.error("Failed to duplicate particle system");629throw new SlickException("Unable to duplicated particle system", e);630}631632return theCopy;633}634}635636637