Home | History | Annotate | Download | only in effect
      1 /*
      2  * Copyright (c) 2009-2012 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 package com.jme3.effect;
     33 
     34 import com.jme3.bounding.BoundingBox;
     35 import com.jme3.effect.ParticleMesh.Type;
     36 import com.jme3.effect.influencers.DefaultParticleInfluencer;
     37 import com.jme3.effect.influencers.ParticleInfluencer;
     38 import com.jme3.effect.shapes.EmitterPointShape;
     39 import com.jme3.effect.shapes.EmitterShape;
     40 import com.jme3.export.InputCapsule;
     41 import com.jme3.export.JmeExporter;
     42 import com.jme3.export.JmeImporter;
     43 import com.jme3.export.OutputCapsule;
     44 import com.jme3.math.ColorRGBA;
     45 import com.jme3.math.FastMath;
     46 import com.jme3.math.Matrix3f;
     47 import com.jme3.math.Vector3f;
     48 import com.jme3.renderer.Camera;
     49 import com.jme3.renderer.RenderManager;
     50 import com.jme3.renderer.ViewPort;
     51 import com.jme3.renderer.queue.RenderQueue.Bucket;
     52 import com.jme3.renderer.queue.RenderQueue.ShadowMode;
     53 import com.jme3.scene.Geometry;
     54 import com.jme3.scene.Spatial;
     55 import com.jme3.scene.control.Control;
     56 import com.jme3.util.TempVars;
     57 import java.io.IOException;
     58 
     59 /**
     60  * <code>ParticleEmitter</code> is a special kind of geometry which simulates
     61  * a particle system.
     62  * <p>
     63  * Particle emitters can be used to simulate various kinds of phenomena,
     64  * such as fire, smoke, explosions and much more.
     65  * <p>
     66  * Particle emitters have many properties which are used to control the
     67  * simulation. The interpretation of these properties depends on the
     68  * {@link ParticleInfluencer} that has been assigned to the emitter via
     69  * {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }.
     70  * By default the implementation {@link DefaultParticleInfluencer} is used.
     71  *
     72  * @author Kirill Vainer
     73  */
     74 public class ParticleEmitter extends Geometry {
     75 
     76     private boolean enabled = true;
     77     private static final EmitterShape DEFAULT_SHAPE = new EmitterPointShape(Vector3f.ZERO);
     78     private static final ParticleInfluencer DEFAULT_INFLUENCER = new DefaultParticleInfluencer();
     79     private ParticleEmitterControl control;
     80     private EmitterShape shape = DEFAULT_SHAPE;
     81     private ParticleMesh particleMesh;
     82     private ParticleInfluencer particleInfluencer = DEFAULT_INFLUENCER;
     83     private ParticleMesh.Type meshType;
     84     private Particle[] particles;
     85     private int firstUnUsed;
     86     private int lastUsed;
     87 //    private int next = 0;
     88 //    private ArrayList<Integer> unusedIndices = new ArrayList<Integer>();
     89     private boolean randomAngle;
     90     private boolean selectRandomImage;
     91     private boolean facingVelocity;
     92     private float particlesPerSec = 20;
     93     private float timeDifference = 0;
     94     private float lowLife = 3f;
     95     private float highLife = 7f;
     96     private Vector3f gravity = new Vector3f(0.0f, 0.1f, 0.0f);
     97     private float rotateSpeed;
     98     private Vector3f faceNormal = new Vector3f(Vector3f.NAN);
     99     private int imagesX = 1;
    100     private int imagesY = 1;
    101 
    102     private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f);
    103     private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f);
    104     private float startSize = 0.2f;
    105     private float endSize = 2f;
    106     private boolean worldSpace = true;
    107     //variable that helps with computations
    108     private transient Vector3f temp = new Vector3f();
    109 
    110     public static class ParticleEmitterControl implements Control {
    111 
    112         ParticleEmitter parentEmitter;
    113 
    114         public ParticleEmitterControl() {
    115         }
    116 
    117         public ParticleEmitterControl(ParticleEmitter parentEmitter) {
    118             this.parentEmitter = parentEmitter;
    119         }
    120 
    121         public Control cloneForSpatial(Spatial spatial) {
    122             return this; // WARNING: Sets wrong control on spatial. Will be
    123             // fixed automatically by ParticleEmitter.clone() method.
    124         }
    125 
    126         public void setSpatial(Spatial spatial) {
    127         }
    128 
    129         public void setEnabled(boolean enabled) {
    130             parentEmitter.setEnabled(enabled);
    131         }
    132 
    133         public boolean isEnabled() {
    134             return parentEmitter.isEnabled();
    135         }
    136 
    137         public void update(float tpf) {
    138             parentEmitter.updateFromControl(tpf);
    139         }
    140 
    141         public void render(RenderManager rm, ViewPort vp) {
    142             parentEmitter.renderFromControl(rm, vp);
    143         }
    144 
    145         public void write(JmeExporter ex) throws IOException {
    146         }
    147 
    148         public void read(JmeImporter im) throws IOException {
    149         }
    150     }
    151 
    152     @Override
    153     public ParticleEmitter clone() {
    154         return clone(true);
    155     }
    156 
    157     @Override
    158     public ParticleEmitter clone(boolean cloneMaterial) {
    159         ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial);
    160         clone.shape = shape.deepClone();
    161 
    162         // Reinitialize particle list
    163         clone.setNumParticles(particles.length);
    164 
    165         clone.faceNormal = faceNormal.clone();
    166         clone.startColor = startColor.clone();
    167         clone.endColor = endColor.clone();
    168         clone.particleInfluencer = particleInfluencer.clone();
    169 
    170         // remove wrong control
    171         clone.controls.remove(control);
    172 
    173         // put correct control
    174         clone.controls.add(new ParticleEmitterControl(clone));
    175 
    176         // Reinitialize particle mesh
    177         switch (meshType) {
    178             case Point:
    179                 clone.particleMesh = new ParticlePointMesh();
    180                 clone.setMesh(clone.particleMesh);
    181                 break;
    182             case Triangle:
    183                 clone.particleMesh = new ParticleTriMesh();
    184                 clone.setMesh(clone.particleMesh);
    185                 break;
    186             default:
    187                 throw new IllegalStateException("Unrecognized particle type: " + meshType);
    188         }
    189         clone.particleMesh.initParticleData(clone, clone.particles.length);
    190         clone.particleMesh.setImagesXY(clone.imagesX, clone.imagesY);
    191 
    192         return clone;
    193     }
    194 
    195     public ParticleEmitter(String name, Type type, int numParticles) {
    196         super(name);
    197 
    198         // ignore world transform, unless user sets inLocalSpace
    199         this.setIgnoreTransform(true);
    200 
    201         // particles neither receive nor cast shadows
    202         this.setShadowMode(ShadowMode.Off);
    203 
    204         // particles are usually transparent
    205         this.setQueueBucket(Bucket.Transparent);
    206 
    207         meshType = type;
    208 
    209         // Must create clone of shape/influencer so that a reference to a static is
    210         // not maintained
    211         shape = shape.deepClone();
    212         particleInfluencer = particleInfluencer.clone();
    213 
    214         control = new ParticleEmitterControl(this);
    215         controls.add(control);
    216 
    217         switch (meshType) {
    218             case Point:
    219                 particleMesh = new ParticlePointMesh();
    220                 this.setMesh(particleMesh);
    221                 break;
    222             case Triangle:
    223                 particleMesh = new ParticleTriMesh();
    224                 this.setMesh(particleMesh);
    225                 break;
    226             default:
    227                 throw new IllegalStateException("Unrecognized particle type: " + meshType);
    228         }
    229         this.setNumParticles(numParticles);
    230 //        particleMesh.initParticleData(this, particles.length);
    231     }
    232 
    233     /**
    234      * For serialization only. Do not use.
    235      */
    236     public ParticleEmitter() {
    237         super();
    238     }
    239 
    240     public void setShape(EmitterShape shape) {
    241         this.shape = shape;
    242     }
    243 
    244     public EmitterShape getShape() {
    245         return shape;
    246     }
    247 
    248     /**
    249      * Set the {@link ParticleInfluencer} to influence this particle emitter.
    250      *
    251      * @param particleInfluencer the {@link ParticleInfluencer} to influence
    252      * this particle emitter.
    253      *
    254      * @see ParticleInfluencer
    255      */
    256     public void setParticleInfluencer(ParticleInfluencer particleInfluencer) {
    257         this.particleInfluencer = particleInfluencer;
    258     }
    259 
    260     /**
    261      * Returns the {@link ParticleInfluencer} that influences this
    262      * particle emitter.
    263      *
    264      * @return the {@link ParticleInfluencer} that influences this
    265      * particle emitter.
    266      *
    267      * @see ParticleInfluencer
    268      */
    269     public ParticleInfluencer getParticleInfluencer() {
    270         return particleInfluencer;
    271     }
    272 
    273     /**
    274      * Returns the mesh type used by the particle emitter.
    275      *
    276      *
    277      * @return the mesh type used by the particle emitter.
    278      *
    279      * @see #setMeshType(com.jme3.effect.ParticleMesh.Type)
    280      * @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int)
    281      */
    282     public ParticleMesh.Type getMeshType() {
    283         return meshType;
    284     }
    285 
    286     /**
    287      * Sets the type of mesh used by the particle emitter.
    288      * @param meshType The mesh type to use
    289      */
    290     public void setMeshType(ParticleMesh.Type meshType) {
    291         this.meshType = meshType;
    292         switch (meshType) {
    293             case Point:
    294                 particleMesh = new ParticlePointMesh();
    295                 this.setMesh(particleMesh);
    296                 break;
    297             case Triangle:
    298                 particleMesh = new ParticleTriMesh();
    299                 this.setMesh(particleMesh);
    300                 break;
    301             default:
    302                 throw new IllegalStateException("Unrecognized particle type: " + meshType);
    303         }
    304         this.setNumParticles(particles.length);
    305     }
    306 
    307     /**
    308      * Returns true if particles should spawn in world space.
    309      *
    310      * @return true if particles should spawn in world space.
    311      *
    312      * @see ParticleEmitter#setInWorldSpace(boolean)
    313      */
    314     public boolean isInWorldSpace() {
    315         return worldSpace;
    316     }
    317 
    318     /**
    319      * Set to true if particles should spawn in world space.
    320      *
    321      * <p>If set to true and the particle emitter is moved in the scene,
    322      * then particles that have already spawned won't be effected by this
    323      * motion. If set to false, the particles will emit in local space
    324      * and when the emitter is moved, so are all the particles that
    325      * were emitted previously.
    326      *
    327      * @param worldSpace true if particles should spawn in world space.
    328      */
    329     public void setInWorldSpace(boolean worldSpace) {
    330         this.setIgnoreTransform(worldSpace);
    331         this.worldSpace = worldSpace;
    332     }
    333 
    334     /**
    335      * Returns the number of visible particles (spawned but not dead).
    336      *
    337      * @return the number of visible particles
    338      */
    339     public int getNumVisibleParticles() {
    340 //        return unusedIndices.size() + next;
    341         return lastUsed + 1;
    342     }
    343 
    344     /**
    345      * Set the maximum amount of particles that
    346      * can exist at the same time with this emitter.
    347      * Calling this method many times is not recommended.
    348      *
    349      * @param numParticles the maximum amount of particles that
    350      * can exist at the same time with this emitter.
    351      */
    352     public final void setNumParticles(int numParticles) {
    353         particles = new Particle[numParticles];
    354         for (int i = 0; i < numParticles; i++) {
    355             particles[i] = new Particle();
    356         }
    357         //We have to reinit the mesh's buffers with the new size
    358         particleMesh.initParticleData(this, particles.length);
    359         particleMesh.setImagesXY(this.imagesX, this.imagesY);
    360         firstUnUsed = 0;
    361         lastUsed = -1;
    362     }
    363 
    364     public int getMaxNumParticles() {
    365         return particles.length;
    366     }
    367 
    368     /**
    369      * Returns a list of all particles (shouldn't be used in most cases).
    370      *
    371      * <p>
    372      * This includes both existing and non-existing particles.
    373      * The size of the array is set to the <code>numParticles</code> value
    374      * specified in the constructor or {@link ParticleEmitter#setNumParticles(int) }
    375      * method.
    376      *
    377      * @return a list of all particles.
    378      */
    379     public Particle[] getParticles() {
    380         return particles;
    381     }
    382 
    383     /**
    384      * Get the normal which particles are facing.
    385      *
    386      * @return the normal which particles are facing.
    387      *
    388      * @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f)
    389      */
    390     public Vector3f getFaceNormal() {
    391         if (Vector3f.isValidVector(faceNormal)) {
    392             return faceNormal;
    393         } else {
    394             return null;
    395         }
    396     }
    397 
    398     /**
    399      * Sets the normal which particles are facing.
    400      *
    401      * <p>By default, particles
    402      * will face the camera, but for some effects (e.g shockwave) it may
    403      * be necessary to face a specific direction instead. To restore
    404      * normal functionality, provide <code>null</code> as the argument for
    405      * <code>faceNormal</code>.
    406      *
    407      * @param faceNormal The normals particles should face, or <code>null</code>
    408      * if particles should face the camera.
    409      */
    410     public void setFaceNormal(Vector3f faceNormal) {
    411         if (faceNormal == null || !Vector3f.isValidVector(faceNormal)) {
    412             this.faceNormal.set(Vector3f.NAN);
    413         } else {
    414             this.faceNormal = faceNormal;
    415         }
    416     }
    417 
    418     /**
    419      * Returns the rotation speed in radians/sec for particles.
    420      *
    421      * @return the rotation speed in radians/sec for particles.
    422      *
    423      * @see ParticleEmitter#setRotateSpeed(float)
    424      */
    425     public float getRotateSpeed() {
    426         return rotateSpeed;
    427     }
    428 
    429     /**
    430      * Set the rotation speed in radians/sec for particles
    431      * spawned after the invocation of this method.
    432      *
    433      * @param rotateSpeed the rotation speed in radians/sec for particles
    434      * spawned after the invocation of this method.
    435      */
    436     public void setRotateSpeed(float rotateSpeed) {
    437         this.rotateSpeed = rotateSpeed;
    438     }
    439 
    440     /**
    441      * Returns true if every particle spawned
    442      * should have a random facing angle.
    443      *
    444      * @return true if every particle spawned
    445      * should have a random facing angle.
    446      *
    447      * @see ParticleEmitter#setRandomAngle(boolean)
    448      */
    449     public boolean isRandomAngle() {
    450         return randomAngle;
    451     }
    452 
    453     /**
    454      * Set to true if every particle spawned
    455      * should have a random facing angle.
    456      *
    457      * @param randomAngle if every particle spawned
    458      * should have a random facing angle.
    459      */
    460     public void setRandomAngle(boolean randomAngle) {
    461         this.randomAngle = randomAngle;
    462     }
    463 
    464     /**
    465      * Returns true if every particle spawned should get a random
    466      * image.
    467      *
    468      * @return True if every particle spawned should get a random
    469      * image.
    470      *
    471      * @see ParticleEmitter#setSelectRandomImage(boolean)
    472      */
    473     public boolean isSelectRandomImage() {
    474         return selectRandomImage;
    475     }
    476 
    477     /**
    478      * Set to true if every particle spawned
    479      * should get a random image from a pool of images constructed from
    480      * the texture, with X by Y possible images.
    481      *
    482      * <p>By default, X and Y are equal
    483      * to 1, thus allowing only 1 possible image to be selected, but if the
    484      * particle is configured with multiple images by using {@link ParticleEmitter#setImagesX(int) }
    485      * and {#link ParticleEmitter#setImagesY(int) } methods, then multiple images
    486      * can be selected. Setting to false will cause each particle to have an animation
    487      * of images displayed, starting at image 1, and going until image X*Y when
    488      * the particle reaches its end of life.
    489      *
    490      * @param selectRandomImage True if every particle spawned should get a random
    491      * image.
    492      */
    493     public void setSelectRandomImage(boolean selectRandomImage) {
    494         this.selectRandomImage = selectRandomImage;
    495     }
    496 
    497     /**
    498      * Check if particles spawned should face their velocity.
    499      *
    500      * @return True if particles spawned should face their velocity.
    501      *
    502      * @see ParticleEmitter#setFacingVelocity(boolean)
    503      */
    504     public boolean isFacingVelocity() {
    505         return facingVelocity;
    506     }
    507 
    508     /**
    509      * Set to true if particles spawned should face
    510      * their velocity (or direction to which they are moving towards).
    511      *
    512      * <p>This is typically used for e.g spark effects.
    513      *
    514      * @param followVelocity True if particles spawned should face their velocity.
    515      *
    516      */
    517     public void setFacingVelocity(boolean followVelocity) {
    518         this.facingVelocity = followVelocity;
    519     }
    520 
    521     /**
    522      * Get the end color of the particles spawned.
    523      *
    524      * @return the end color of the particles spawned.
    525      *
    526      * @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA)
    527      */
    528     public ColorRGBA getEndColor() {
    529         return endColor;
    530     }
    531 
    532     /**
    533      * Set the end color of the particles spawned.
    534      *
    535      * <p>The
    536      * particle color at any time is determined by blending the start color
    537      * and end color based on the particle's current time of life relative
    538      * to its end of life.
    539      *
    540      * @param endColor the end color of the particles spawned.
    541      */
    542     public void setEndColor(ColorRGBA endColor) {
    543         this.endColor.set(endColor);
    544     }
    545 
    546     /**
    547      * Get the end size of the particles spawned.
    548      *
    549      * @return the end size of the particles spawned.
    550      *
    551      * @see ParticleEmitter#setEndSize(float)
    552      */
    553     public float getEndSize() {
    554         return endSize;
    555     }
    556 
    557     /**
    558      * Set the end size of the particles spawned.
    559      *
    560      * <p>The
    561      * particle size at any time is determined by blending the start size
    562      * and end size based on the particle's current time of life relative
    563      * to its end of life.
    564      *
    565      * @param endSize the end size of the particles spawned.
    566      */
    567     public void setEndSize(float endSize) {
    568         this.endSize = endSize;
    569     }
    570 
    571     /**
    572      * Get the gravity vector.
    573      *
    574      * @return the gravity vector.
    575      *
    576      * @see ParticleEmitter#setGravity(com.jme3.math.Vector3f)
    577      */
    578     public Vector3f getGravity() {
    579         return gravity;
    580     }
    581 
    582     /**
    583      * This method sets the gravity vector.
    584      *
    585      * @param gravity the gravity vector
    586      */
    587     public void setGravity(Vector3f gravity) {
    588         this.gravity.set(gravity);
    589     }
    590 
    591     /**
    592      * Sets the gravity vector.
    593      *
    594      * @param x the x component of the gravity vector
    595      * @param y the y component of the gravity vector
    596      * @param z the z component of the gravity vector
    597      */
    598     public void setGravity(float x, float y, float z) {
    599         this.gravity.x = x;
    600         this.gravity.y = y;
    601         this.gravity.z = z;
    602     }
    603 
    604     /**
    605      * Get the high value of life.
    606      *
    607      * @return the high value of life.
    608      *
    609      * @see ParticleEmitter#setHighLife(float)
    610      */
    611     public float getHighLife() {
    612         return highLife;
    613     }
    614 
    615     /**
    616      * Set the high value of life.
    617      *
    618      * <p>The particle's lifetime/expiration
    619      * is determined by randomly selecting a time between low life and high life.
    620      *
    621      * @param highLife the high value of life.
    622      */
    623     public void setHighLife(float highLife) {
    624         this.highLife = highLife;
    625     }
    626 
    627     /**
    628      * Get the number of images along the X axis (width).
    629      *
    630      * @return the number of images along the X axis (width).
    631      *
    632      * @see ParticleEmitter#setImagesX(int)
    633      */
    634     public int getImagesX() {
    635         return imagesX;
    636     }
    637 
    638     /**
    639      * Set the number of images along the X axis (width).
    640      *
    641      * <p>To determine
    642      * how multiple particle images are selected and used, see the
    643      * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
    644      *
    645      * @param imagesX the number of images along the X axis (width).
    646      */
    647     public void setImagesX(int imagesX) {
    648         this.imagesX = imagesX;
    649         particleMesh.setImagesXY(this.imagesX, this.imagesY);
    650     }
    651 
    652     /**
    653      * Get the number of images along the Y axis (height).
    654      *
    655      * @return the number of images along the Y axis (height).
    656      *
    657      * @see ParticleEmitter#setImagesY(int)
    658      */
    659     public int getImagesY() {
    660         return imagesY;
    661     }
    662 
    663     /**
    664      * Set the number of images along the Y axis (height).
    665      *
    666      * <p>To determine how multiple particle images are selected and used, see the
    667      * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
    668      *
    669      * @param imagesY the number of images along the Y axis (height).
    670      */
    671     public void setImagesY(int imagesY) {
    672         this.imagesY = imagesY;
    673         particleMesh.setImagesXY(this.imagesX, this.imagesY);
    674     }
    675 
    676     /**
    677      * Get the low value of life.
    678      *
    679      * @return the low value of life.
    680      *
    681      * @see ParticleEmitter#setLowLife(float)
    682      */
    683     public float getLowLife() {
    684         return lowLife;
    685     }
    686 
    687     /**
    688      * Set the low value of life.
    689      *
    690      * <p>The particle's lifetime/expiration
    691      * is determined by randomly selecting a time between low life and high life.
    692      *
    693      * @param lowLife the low value of life.
    694      */
    695     public void setLowLife(float lowLife) {
    696         this.lowLife = lowLife;
    697     }
    698 
    699     /**
    700      * Get the number of particles to spawn per
    701      * second.
    702      *
    703      * @return the number of particles to spawn per
    704      * second.
    705      *
    706      * @see ParticleEmitter#setParticlesPerSec(float)
    707      */
    708     public float getParticlesPerSec() {
    709         return particlesPerSec;
    710     }
    711 
    712     /**
    713      * Set the number of particles to spawn per
    714      * second.
    715      *
    716      * @param particlesPerSec the number of particles to spawn per
    717      * second.
    718      */
    719     public void setParticlesPerSec(float particlesPerSec) {
    720         this.particlesPerSec = particlesPerSec;
    721     }
    722 
    723     /**
    724      * Get the start color of the particles spawned.
    725      *
    726      * @return the start color of the particles spawned.
    727      *
    728      * @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA)
    729      */
    730     public ColorRGBA getStartColor() {
    731         return startColor;
    732     }
    733 
    734     /**
    735      * Set the start color of the particles spawned.
    736      *
    737      * <p>The particle color at any time is determined by blending the start color
    738      * and end color based on the particle's current time of life relative
    739      * to its end of life.
    740      *
    741      * @param startColor the start color of the particles spawned
    742      */
    743     public void setStartColor(ColorRGBA startColor) {
    744         this.startColor.set(startColor);
    745     }
    746 
    747     /**
    748      * Get the start color of the particles spawned.
    749      *
    750      * @return the start color of the particles spawned.
    751      *
    752      * @see ParticleEmitter#setStartSize(float)
    753      */
    754     public float getStartSize() {
    755         return startSize;
    756     }
    757 
    758     /**
    759      * Set the start size of the particles spawned.
    760      *
    761      * <p>The particle size at any time is determined by blending the start size
    762      * and end size based on the particle's current time of life relative
    763      * to its end of life.
    764      *
    765      * @param startSize the start size of the particles spawned.
    766      */
    767     public void setStartSize(float startSize) {
    768         this.startSize = startSize;
    769     }
    770 
    771     /**
    772      * @deprecated Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead.
    773      */
    774     @Deprecated
    775     public Vector3f getInitialVelocity() {
    776         return particleInfluencer.getInitialVelocity();
    777     }
    778 
    779     /**
    780      * @param initialVelocity Set the initial velocity a particle is spawned with,
    781      * the initial velocity given in the parameter will be varied according
    782      * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }.
    783      * A particle will move toward its velocity unless it is effected by the
    784      * gravity.
    785      *
    786      * @deprecated
    787      * This method is deprecated.
    788      * Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead.
    789      *
    790      * @see ParticleEmitter#setVelocityVariation(float)
    791      * @see ParticleEmitter#setGravity(float)
    792      */
    793     @Deprecated
    794     public void setInitialVelocity(Vector3f initialVelocity) {
    795         this.particleInfluencer.setInitialVelocity(initialVelocity);
    796     }
    797 
    798     /**
    799      * @deprecated
    800      * This method is deprecated.
    801      * Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead.
    802      * @return the initial velocity variation factor
    803      */
    804     @Deprecated
    805     public float getVelocityVariation() {
    806         return particleInfluencer.getVelocityVariation();
    807     }
    808 
    809     /**
    810      * @param variation Set the variation by which the initial velocity
    811      * of the particle is determined. <code>variation</code> should be a value
    812      * from 0 to 1, where 0 means particles are to spawn with exactly
    813      * the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
    814      * and 1 means particles are to spawn with a completely random velocity.
    815      *
    816      * @deprecated
    817      * This method is deprecated.
    818      * Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead.
    819      */
    820     @Deprecated
    821     public void setVelocityVariation(float variation) {
    822         this.particleInfluencer.setVelocityVariation(variation);
    823     }
    824 
    825     private Particle emitParticle(Vector3f min, Vector3f max) {
    826         int idx = lastUsed + 1;
    827         if (idx >= particles.length) {
    828             return null;
    829         }
    830 
    831         Particle p = particles[idx];
    832         if (selectRandomImage) {
    833             p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) * imagesX + FastMath.nextRandomInt(0, imagesX - 1);
    834         }
    835 
    836         p.startlife = lowLife + FastMath.nextRandomFloat() * (highLife - lowLife);
    837         p.life = p.startlife;
    838         p.color.set(startColor);
    839         p.size = startSize;
    840         //shape.getRandomPoint(p.position);
    841         particleInfluencer.influenceParticle(p, shape);
    842         if (worldSpace) {
    843             worldTransform.transformVector(p.position, p.position);
    844             worldTransform.getRotation().mult(p.velocity, p.velocity);
    845             // TODO: Make scale relevant somehow??
    846         }
    847         if (randomAngle) {
    848             p.angle = FastMath.nextRandomFloat() * FastMath.TWO_PI;
    849         }
    850         if (rotateSpeed != 0) {
    851             p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f);
    852         }
    853 
    854         temp.set(p.position).addLocal(p.size, p.size, p.size);
    855         max.maxLocal(temp);
    856         temp.set(p.position).subtractLocal(p.size, p.size, p.size);
    857         min.minLocal(temp);
    858 
    859         ++lastUsed;
    860         firstUnUsed = idx + 1;
    861         return p;
    862     }
    863 
    864     /**
    865      * Instantly emits all the particles possible to be emitted. Any particles
    866      * which are currently inactive will be spawned immediately.
    867      */
    868     public void emitAllParticles() {
    869         // Force world transform to update
    870         this.getWorldTransform();
    871 
    872         TempVars vars = TempVars.get();
    873 
    874         BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
    875 
    876         Vector3f min = vars.vect1;
    877         Vector3f max = vars.vect2;
    878 
    879         bbox.getMin(min);
    880         bbox.getMax(max);
    881 
    882         if (!Vector3f.isValidVector(min)) {
    883             min.set(Vector3f.POSITIVE_INFINITY);
    884         }
    885         if (!Vector3f.isValidVector(max)) {
    886             max.set(Vector3f.NEGATIVE_INFINITY);
    887         }
    888 
    889         while (emitParticle(min, max) != null);
    890 
    891         bbox.setMinMax(min, max);
    892         this.setBoundRefresh();
    893 
    894         vars.release();
    895     }
    896 
    897     /**
    898      * Instantly kills all active particles, after this method is called, all
    899      * particles will be dead and no longer visible.
    900      */
    901     public void killAllParticles() {
    902         for (int i = 0; i < particles.length; ++i) {
    903             if (particles[i].life > 0) {
    904                 this.freeParticle(i);
    905             }
    906         }
    907     }
    908 
    909     /**
    910      * Kills the particle at the given index.
    911      *
    912      * @param index The index of the particle to kill
    913      * @see #getParticles()
    914      */
    915     public void killParticle(int index){
    916         freeParticle(index);
    917     }
    918 
    919     private void freeParticle(int idx) {
    920         Particle p = particles[idx];
    921         p.life = 0;
    922         p.size = 0f;
    923         p.color.set(0, 0, 0, 0);
    924         p.imageIndex = 0;
    925         p.angle = 0;
    926         p.rotateSpeed = 0;
    927 
    928         if (idx == lastUsed) {
    929             while (lastUsed >= 0 && particles[lastUsed].life == 0) {
    930                 lastUsed--;
    931             }
    932         }
    933         if (idx < firstUnUsed) {
    934             firstUnUsed = idx;
    935         }
    936     }
    937 
    938     private void swap(int idx1, int idx2) {
    939         Particle p1 = particles[idx1];
    940         particles[idx1] = particles[idx2];
    941         particles[idx2] = p1;
    942     }
    943 
    944     private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){
    945         // applying gravity
    946         p.velocity.x -= gravity.x * tpf;
    947         p.velocity.y -= gravity.y * tpf;
    948         p.velocity.z -= gravity.z * tpf;
    949         temp.set(p.velocity).multLocal(tpf);
    950         p.position.addLocal(temp);
    951 
    952         // affecting color, size and angle
    953         float b = (p.startlife - p.life) / p.startlife;
    954         p.color.interpolate(startColor, endColor, b);
    955         p.size = FastMath.interpolateLinear(b, startSize, endSize);
    956         p.angle += p.rotateSpeed * tpf;
    957 
    958         // Computing bounding volume
    959         temp.set(p.position).addLocal(p.size, p.size, p.size);
    960         max.maxLocal(temp);
    961         temp.set(p.position).subtractLocal(p.size, p.size, p.size);
    962         min.minLocal(temp);
    963 
    964         if (!selectRandomImage) {
    965             p.imageIndex = (int) (b * imagesX * imagesY);
    966         }
    967     }
    968 
    969     private void updateParticleState(float tpf) {
    970         // Force world transform to update
    971         this.getWorldTransform();
    972 
    973         TempVars vars = TempVars.get();
    974 
    975         Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY);
    976         Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY);
    977 
    978         for (int i = 0; i < particles.length; ++i) {
    979             Particle p = particles[i];
    980             if (p.life == 0) { // particle is dead
    981 //                assert i <= firstUnUsed;
    982                 continue;
    983             }
    984 
    985             p.life -= tpf;
    986             if (p.life <= 0) {
    987                 this.freeParticle(i);
    988                 continue;
    989             }
    990 
    991             updateParticle(p, tpf, min, max);
    992 
    993             if (firstUnUsed < i) {
    994                 this.swap(firstUnUsed, i);
    995                 if (i == lastUsed) {
    996                     lastUsed = firstUnUsed;
    997                 }
    998                 firstUnUsed++;
    999             }
   1000         }
   1001 
   1002         // Spawns particles within the tpf timeslot with proper age
   1003         float interval = 1f / particlesPerSec;
   1004         tpf += timeDifference;
   1005         while (tpf > interval){
   1006             tpf -= interval;
   1007             Particle p = emitParticle(min, max);
   1008             if (p != null){
   1009                 p.life -= tpf;
   1010                 if (p.life <= 0){
   1011                     freeParticle(lastUsed);
   1012                 }else{
   1013                     updateParticle(p, tpf, min, max);
   1014                 }
   1015             }
   1016         }
   1017         timeDifference = tpf;
   1018 
   1019         BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
   1020         bbox.setMinMax(min, max);
   1021         this.setBoundRefresh();
   1022 
   1023         vars.release();
   1024     }
   1025 
   1026     /**
   1027      * Set to enable or disable the particle emitter
   1028      *
   1029      * <p>When a particle is
   1030      * disabled, it will be "frozen in time" and not update.
   1031      *
   1032      * @param enabled True to enable the particle emitter
   1033      */
   1034     public void setEnabled(boolean enabled) {
   1035         this.enabled = enabled;
   1036     }
   1037 
   1038     /**
   1039      * Check if a particle emitter is enabled for update.
   1040      *
   1041      * @return True if a particle emitter is enabled for update.
   1042      *
   1043      * @see ParticleEmitter#setEnabled(boolean)
   1044      */
   1045     public boolean isEnabled() {
   1046         return enabled;
   1047     }
   1048 
   1049     /**
   1050      * Callback from Control.update(), do not use.
   1051      * @param tpf
   1052      */
   1053     public void updateFromControl(float tpf) {
   1054         if (enabled) {
   1055             this.updateParticleState(tpf);
   1056         }
   1057     }
   1058 
   1059     /**
   1060      * Callback from Control.render(), do not use.
   1061      *
   1062      * @param rm
   1063      * @param vp
   1064      */
   1065     private void renderFromControl(RenderManager rm, ViewPort vp) {
   1066         Camera cam = vp.getCamera();
   1067 
   1068         if (meshType == ParticleMesh.Type.Point) {
   1069             float C = cam.getProjectionMatrix().m00;
   1070             C *= cam.getWidth() * 0.5f;
   1071 
   1072             // send attenuation params
   1073             this.getMaterial().setFloat("Quadratic", C);
   1074         }
   1075 
   1076         Matrix3f inverseRotation = Matrix3f.IDENTITY;
   1077         TempVars vars = null;
   1078         if (!worldSpace) {
   1079             vars = TempVars.get();
   1080 
   1081             inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
   1082         }
   1083         particleMesh.updateParticleData(particles, cam, inverseRotation);
   1084         if (!worldSpace) {
   1085             vars.release();
   1086         }
   1087     }
   1088 
   1089     public void preload(RenderManager rm, ViewPort vp) {
   1090         this.updateParticleState(0);
   1091         particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
   1092     }
   1093 
   1094     @Override
   1095     public void write(JmeExporter ex) throws IOException {
   1096         super.write(ex);
   1097         OutputCapsule oc = ex.getCapsule(this);
   1098         oc.write(shape, "shape", DEFAULT_SHAPE);
   1099         oc.write(meshType, "meshType", ParticleMesh.Type.Triangle);
   1100         oc.write(enabled, "enabled", true);
   1101         oc.write(particles.length, "numParticles", 0);
   1102         oc.write(particlesPerSec, "particlesPerSec", 0);
   1103         oc.write(lowLife, "lowLife", 0);
   1104         oc.write(highLife, "highLife", 0);
   1105         oc.write(gravity, "gravity", null);
   1106         oc.write(imagesX, "imagesX", 1);
   1107         oc.write(imagesY, "imagesY", 1);
   1108 
   1109         oc.write(startColor, "startColor", null);
   1110         oc.write(endColor, "endColor", null);
   1111         oc.write(startSize, "startSize", 0);
   1112         oc.write(endSize, "endSize", 0);
   1113         oc.write(worldSpace, "worldSpace", false);
   1114         oc.write(facingVelocity, "facingVelocity", false);
   1115         oc.write(faceNormal, "faceNormal", new Vector3f(Vector3f.NAN));
   1116         oc.write(selectRandomImage, "selectRandomImage", false);
   1117         oc.write(randomAngle, "randomAngle", false);
   1118         oc.write(rotateSpeed, "rotateSpeed", 0);
   1119 
   1120         oc.write(particleInfluencer, "influencer", DEFAULT_INFLUENCER);
   1121     }
   1122 
   1123     @Override
   1124     public void read(JmeImporter im) throws IOException {
   1125         super.read(im);
   1126         InputCapsule ic = im.getCapsule(this);
   1127         shape = (EmitterShape) ic.readSavable("shape", DEFAULT_SHAPE);
   1128 
   1129         if (shape == DEFAULT_SHAPE) {
   1130             // Prevent reference to static
   1131             shape = shape.deepClone();
   1132         }
   1133 
   1134         meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle);
   1135         int numParticles = ic.readInt("numParticles", 0);
   1136 
   1137 
   1138         enabled = ic.readBoolean("enabled", true);
   1139         particlesPerSec = ic.readFloat("particlesPerSec", 0);
   1140         lowLife = ic.readFloat("lowLife", 0);
   1141         highLife = ic.readFloat("highLife", 0);
   1142         gravity = (Vector3f) ic.readSavable("gravity", null);
   1143         imagesX = ic.readInt("imagesX", 1);
   1144         imagesY = ic.readInt("imagesY", 1);
   1145 
   1146         startColor = (ColorRGBA) ic.readSavable("startColor", null);
   1147         endColor = (ColorRGBA) ic.readSavable("endColor", null);
   1148         startSize = ic.readFloat("startSize", 0);
   1149         endSize = ic.readFloat("endSize", 0);
   1150         worldSpace = ic.readBoolean("worldSpace", false);
   1151         this.setIgnoreTransform(worldSpace);
   1152         facingVelocity = ic.readBoolean("facingVelocity", false);
   1153         faceNormal = (Vector3f)ic.readSavable("faceNormal", new Vector3f(Vector3f.NAN));
   1154         selectRandomImage = ic.readBoolean("selectRandomImage", false);
   1155         randomAngle = ic.readBoolean("randomAngle", false);
   1156         rotateSpeed = ic.readFloat("rotateSpeed", 0);
   1157 
   1158         switch (meshType) {
   1159             case Point:
   1160                 particleMesh = new ParticlePointMesh();
   1161                 this.setMesh(particleMesh);
   1162                 break;
   1163             case Triangle:
   1164                 particleMesh = new ParticleTriMesh();
   1165                 this.setMesh(particleMesh);
   1166                 break;
   1167             default:
   1168                 throw new IllegalStateException("Unrecognized particle type: " + meshType);
   1169         }
   1170         this.setNumParticles(numParticles);
   1171 //        particleMesh.initParticleData(this, particles.length);
   1172 //        particleMesh.setImagesXY(imagesX, imagesY);
   1173 
   1174         particleInfluencer = (ParticleInfluencer) ic.readSavable("influencer", DEFAULT_INFLUENCER);
   1175         if (particleInfluencer == DEFAULT_INFLUENCER) {
   1176             particleInfluencer = particleInfluencer.clone();
   1177         }
   1178 
   1179         if (im.getFormatVersion() == 0) {
   1180             // compatibility before the control inside particle emitter
   1181             // was changed:
   1182             // find it in the controls and take it out, then add the proper one in
   1183             for (int i = 0; i < controls.size(); i++) {
   1184                 Object obj = controls.get(i);
   1185                 if (obj instanceof ParticleEmitter) {
   1186                     controls.remove(i);
   1187                     // now add the proper one in
   1188                     controls.add(new ParticleEmitterControl(this));
   1189                     break;
   1190                 }
   1191             }
   1192 
   1193             // compatability before gravity was not a vector but a float
   1194             if (gravity == null) {
   1195                 gravity = new Vector3f();
   1196                 gravity.y = ic.readFloat("gravity", 0);
   1197             }
   1198         } else {
   1199             // since the parentEmitter is not loaded, it must be
   1200             // loaded separately
   1201             control = getControl(ParticleEmitterControl.class);
   1202             control.parentEmitter = this;
   1203 
   1204         }
   1205     }
   1206 }
   1207