All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.mathiewz.slick.particles.ParticleSystem Maven / Gradle / Ivy

Go to download

The main purpose of this libraryis to modernize and maintain the slick2D library.

The newest version!
package com.github.mathiewz.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.lwjgl.opengl.GL11;

import com.github.mathiewz.slick.Color;
import com.github.mathiewz.slick.Image;
import com.github.mathiewz.slick.SlickException;
import com.github.mathiewz.slick.opengl.TextureImpl;
import com.github.mathiewz.slick.opengl.renderer.Renderer;
import com.github.mathiewz.slick.opengl.renderer.SGL;
import com.github.mathiewz.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 final 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 (Particle particle : particles) {
                available.add(particle);
            }
        }
    }
    
    /**
     * 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 = pools.next();
            pool.reset(this);
        }
        
        for (int i = 0; i < emitters.size(); i++) {
            ParticleEmitter emitter = 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) {
        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) {
        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) {
        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 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(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(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
        }
        if (usePoints()) {
            GL.glEnable(GL11.GL_POINT_SMOOTH);
            TextureImpl.bindNone();
        }
        
        // iterate over all emitters
        for (int emitterIdx = 0; emitterIdx < emitters.size(); emitterIdx++) {
            // get emitter
            ParticleEmitter emitter = emitters.get(emitterIdx);
            
            if (!emitter.isEnabled()) {
                continue;
            }
            
            // check for additive override and enable when set
            if (emitter.useAdditive()) {
                GL.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE);
            }
            
            // now get the particle pool for this emitter and render all particles that are in use
            ParticlePool pool = particlesByEmitter.get(emitter);
            Image image = emitter.getImage();
            if (image == null) {
                image = sprite;
            }
            
            if (!emitter.isOriented() && !emitter.usePoints(this)) {
                image.startUse();
            }
            
            for (Particle particle : pool.particles) {
                if (particle.inUse()) {
                    particle.render();
                }
            }
            
            if (!emitter.isOriented() && !emitter.usePoints(this)) {
                image.endUse();
            }
            
            // reset additive blend mode
            if (emitter.useAdditive()) {
                GL.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
            }
        }
        
        if (usePoints()) {
            GL.glDisable(GL11.GL_POINT_SMOOTH);
        }
        if (blendingMode == BLEND_ADDITIVE) {
            GL.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.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((PrivilegedAction) () -> {
            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 = 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 = it.next();
                if (emitter.isEnabled()) {
                    ParticlePool pool = particlesByEmitter.get(emitter);
                    for (Particle particle : pool.particles) {
                        if (particle.life > 0) {
                            particle.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 = particlesByEmitter.get(emitter);
        ArrayList available = pool.available;
        if (available.size() > 0) {
            Particle p = 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 = 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 = it.next();
                for (Particle particle : pool.particles) {
                    if (particle.inUse()) {
                        if (particle.getEmitter() == emitter) {
                            particle.setLife(-1);
                            release(particle);
                        }
                    }
                }
            }
        }
    }
    
    /**
     * 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 = particlesByEmitter.get(emitter);
        for (Particle particle : pool.particles) {
            if (particle.inUse()) {
                particle.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
     */
    public ParticleSystem duplicate() {
        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;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy