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 */
32package com.jme3.effect;
33
34import com.jme3.bounding.BoundingBox;
35import com.jme3.effect.ParticleMesh.Type;
36import com.jme3.effect.influencers.DefaultParticleInfluencer;
37import com.jme3.effect.influencers.ParticleInfluencer;
38import com.jme3.effect.shapes.EmitterPointShape;
39import com.jme3.effect.shapes.EmitterShape;
40import com.jme3.export.InputCapsule;
41import com.jme3.export.JmeExporter;
42import com.jme3.export.JmeImporter;
43import com.jme3.export.OutputCapsule;
44import com.jme3.math.ColorRGBA;
45import com.jme3.math.FastMath;
46import com.jme3.math.Matrix3f;
47import com.jme3.math.Vector3f;
48import com.jme3.renderer.Camera;
49import com.jme3.renderer.RenderManager;
50import com.jme3.renderer.ViewPort;
51import com.jme3.renderer.queue.RenderQueue.Bucket;
52import com.jme3.renderer.queue.RenderQueue.ShadowMode;
53import com.jme3.scene.Geometry;
54import com.jme3.scene.Spatial;
55import com.jme3.scene.control.Control;
56import com.jme3.util.TempVars;
57import 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 */
74public 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