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 
ParticleEmitterControl()114         public ParticleEmitterControl() {
115         }
116 
ParticleEmitterControl(ParticleEmitter parentEmitter)117         public ParticleEmitterControl(ParticleEmitter parentEmitter) {
118             this.parentEmitter = parentEmitter;
119         }
120 
cloneForSpatial(Spatial spatial)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 
setSpatial(Spatial spatial)126         public void setSpatial(Spatial spatial) {
127         }
128 
setEnabled(boolean enabled)129         public void setEnabled(boolean enabled) {
130             parentEmitter.setEnabled(enabled);
131         }
132 
isEnabled()133         public boolean isEnabled() {
134             return parentEmitter.isEnabled();
135         }
136 
update(float tpf)137         public void update(float tpf) {
138             parentEmitter.updateFromControl(tpf);
139         }
140 
render(RenderManager rm, ViewPort vp)141         public void render(RenderManager rm, ViewPort vp) {
142             parentEmitter.renderFromControl(rm, vp);
143         }
144 
write(JmeExporter ex)145         public void write(JmeExporter ex) throws IOException {
146         }
147 
read(JmeImporter im)148         public void read(JmeImporter im) throws IOException {
149         }
150     }
151 
152     @Override
clone()153     public ParticleEmitter clone() {
154         return clone(true);
155     }
156 
157     @Override
clone(boolean cloneMaterial)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 
ParticleEmitter(String name, Type type, int numParticles)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      */
ParticleEmitter()236     public ParticleEmitter() {
237         super();
238     }
239 
setShape(EmitterShape shape)240     public void setShape(EmitterShape shape) {
241         this.shape = shape;
242     }
243 
getShape()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      */
setParticleInfluencer(ParticleInfluencer particleInfluencer)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      */
getParticleInfluencer()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      */
getMeshType()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      */
setMeshType(ParticleMesh.Type meshType)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      */
isInWorldSpace()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      */
setInWorldSpace(boolean worldSpace)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      */
getNumVisibleParticles()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      */
setNumParticles(int numParticles)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 
getMaxNumParticles()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      */
getParticles()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      */
getFaceNormal()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      */
setFaceNormal(Vector3f faceNormal)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      */
getRotateSpeed()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      */
setRotateSpeed(float rotateSpeed)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      */
isRandomAngle()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      */
setRandomAngle(boolean randomAngle)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      */
isSelectRandomImage()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      */
setSelectRandomImage(boolean selectRandomImage)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      */
isFacingVelocity()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      */
setFacingVelocity(boolean followVelocity)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      */
getEndColor()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      */
setEndColor(ColorRGBA endColor)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      */
getEndSize()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      */
setEndSize(float endSize)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      */
getGravity()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      */
setGravity(Vector3f gravity)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      */
setGravity(float x, float y, float z)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      */
getHighLife()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      */
setHighLife(float highLife)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      */
getImagesX()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      */
setImagesX(int imagesX)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      */
getImagesY()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      */
setImagesY(int imagesY)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      */
getLowLife()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      */
setLowLife(float lowLife)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      */
getParticlesPerSec()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      */
setParticlesPerSec(float particlesPerSec)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      */
getStartColor()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      */
setStartColor(ColorRGBA startColor)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      */
getStartSize()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      */
setStartSize(float startSize)767     public void setStartSize(float startSize) {
768         this.startSize = startSize;
769     }
770 
771     /**
772      * @deprecated Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead.
773      */
774     @Deprecated
getInitialVelocity()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
setInitialVelocity(Vector3f initialVelocity)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
getVelocityVariation()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
setVelocityVariation(float variation)821     public void setVelocityVariation(float variation) {
822         this.particleInfluencer.setVelocityVariation(variation);
823     }
824 
emitParticle(Vector3f min, Vector3f max)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      */
emitAllParticles()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      */
killAllParticles()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      */
killParticle(int index)915     public void killParticle(int index){
916         freeParticle(index);
917     }
918 
freeParticle(int idx)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 
swap(int idx1, int idx2)938     private void swap(int idx1, int idx2) {
939         Particle p1 = particles[idx1];
940         particles[idx1] = particles[idx2];
941         particles[idx2] = p1;
942     }
943 
updateParticle(Particle p, float tpf, Vector3f min, Vector3f max)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 
updateParticleState(float tpf)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      */
setEnabled(boolean enabled)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      */
isEnabled()1045     public boolean isEnabled() {
1046         return enabled;
1047     }
1048 
1049     /**
1050      * Callback from Control.update(), do not use.
1051      * @param tpf
1052      */
updateFromControl(float tpf)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      */
renderFromControl(RenderManager rm, ViewPort vp)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 
preload(RenderManager rm, ViewPort vp)1089     public void preload(RenderManager rm, ViewPort vp) {
1090         this.updateParticleState(0);
1091         particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
1092     }
1093 
1094     @Override
write(JmeExporter ex)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
read(JmeImporter im)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