aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/effect/ParticleEmitter.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/effect/ParticleEmitter.java')
-rw-r--r--engine/src/core/com/jme3/effect/ParticleEmitter.java1206
1 files changed, 1206 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/effect/ParticleEmitter.java b/engine/src/core/com/jme3/effect/ParticleEmitter.java
new file mode 100644
index 0000000..c00e66e
--- /dev/null
+++ b/engine/src/core/com/jme3/effect/ParticleEmitter.java
@@ -0,0 +1,1206 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.effect;
+
+import com.jme3.bounding.BoundingBox;
+import com.jme3.effect.ParticleMesh.Type;
+import com.jme3.effect.influencers.DefaultParticleInfluencer;
+import com.jme3.effect.influencers.ParticleInfluencer;
+import com.jme3.effect.shapes.EmitterPointShape;
+import com.jme3.effect.shapes.EmitterShape;
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix3f;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.Camera;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.renderer.queue.RenderQueue.Bucket;
+import com.jme3.renderer.queue.RenderQueue.ShadowMode;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+
+/**
+ * <code>ParticleEmitter</code> is a special kind of geometry which simulates
+ * a particle system.
+ * <p>
+ * Particle emitters can be used to simulate various kinds of phenomena,
+ * such as fire, smoke, explosions and much more.
+ * <p>
+ * Particle emitters have many properties which are used to control the
+ * simulation. The interpretation of these properties depends on the
+ * {@link ParticleInfluencer} that has been assigned to the emitter via
+ * {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }.
+ * By default the implementation {@link DefaultParticleInfluencer} is used.
+ *
+ * @author Kirill Vainer
+ */
+public class ParticleEmitter extends Geometry {
+
+ private boolean enabled = true;
+ private static final EmitterShape DEFAULT_SHAPE = new EmitterPointShape(Vector3f.ZERO);
+ private static final ParticleInfluencer DEFAULT_INFLUENCER = new DefaultParticleInfluencer();
+ private ParticleEmitterControl control;
+ private EmitterShape shape = DEFAULT_SHAPE;
+ private ParticleMesh particleMesh;
+ private ParticleInfluencer particleInfluencer = DEFAULT_INFLUENCER;
+ private ParticleMesh.Type meshType;
+ private Particle[] particles;
+ private int firstUnUsed;
+ private int lastUsed;
+// private int next = 0;
+// private ArrayList<Integer> unusedIndices = new ArrayList<Integer>();
+ private boolean randomAngle;
+ private boolean selectRandomImage;
+ private boolean facingVelocity;
+ private float particlesPerSec = 20;
+ private float timeDifference = 0;
+ private float lowLife = 3f;
+ private float highLife = 7f;
+ private Vector3f gravity = new Vector3f(0.0f, 0.1f, 0.0f);
+ private float rotateSpeed;
+ private Vector3f faceNormal = new Vector3f(Vector3f.NAN);
+ private int imagesX = 1;
+ private int imagesY = 1;
+
+ private ColorRGBA startColor = new ColorRGBA(0.4f, 0.4f, 0.4f, 0.5f);
+ private ColorRGBA endColor = new ColorRGBA(0.1f, 0.1f, 0.1f, 0.0f);
+ private float startSize = 0.2f;
+ private float endSize = 2f;
+ private boolean worldSpace = true;
+ //variable that helps with computations
+ private transient Vector3f temp = new Vector3f();
+
+ public static class ParticleEmitterControl implements Control {
+
+ ParticleEmitter parentEmitter;
+
+ public ParticleEmitterControl() {
+ }
+
+ public ParticleEmitterControl(ParticleEmitter parentEmitter) {
+ this.parentEmitter = parentEmitter;
+ }
+
+ public Control cloneForSpatial(Spatial spatial) {
+ return this; // WARNING: Sets wrong control on spatial. Will be
+ // fixed automatically by ParticleEmitter.clone() method.
+ }
+
+ public void setSpatial(Spatial spatial) {
+ }
+
+ public void setEnabled(boolean enabled) {
+ parentEmitter.setEnabled(enabled);
+ }
+
+ public boolean isEnabled() {
+ return parentEmitter.isEnabled();
+ }
+
+ public void update(float tpf) {
+ parentEmitter.updateFromControl(tpf);
+ }
+
+ public void render(RenderManager rm, ViewPort vp) {
+ parentEmitter.renderFromControl(rm, vp);
+ }
+
+ public void write(JmeExporter ex) throws IOException {
+ }
+
+ public void read(JmeImporter im) throws IOException {
+ }
+ }
+
+ @Override
+ public ParticleEmitter clone() {
+ return clone(true);
+ }
+
+ @Override
+ public ParticleEmitter clone(boolean cloneMaterial) {
+ ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial);
+ clone.shape = shape.deepClone();
+
+ // Reinitialize particle list
+ clone.setNumParticles(particles.length);
+
+ clone.faceNormal = faceNormal.clone();
+ clone.startColor = startColor.clone();
+ clone.endColor = endColor.clone();
+ clone.particleInfluencer = particleInfluencer.clone();
+
+ // remove wrong control
+ clone.controls.remove(control);
+
+ // put correct control
+ clone.controls.add(new ParticleEmitterControl(clone));
+
+ // Reinitialize particle mesh
+ switch (meshType) {
+ case Point:
+ clone.particleMesh = new ParticlePointMesh();
+ clone.setMesh(clone.particleMesh);
+ break;
+ case Triangle:
+ clone.particleMesh = new ParticleTriMesh();
+ clone.setMesh(clone.particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ clone.particleMesh.initParticleData(clone, clone.particles.length);
+ clone.particleMesh.setImagesXY(clone.imagesX, clone.imagesY);
+
+ return clone;
+ }
+
+ public ParticleEmitter(String name, Type type, int numParticles) {
+ super(name);
+
+ // ignore world transform, unless user sets inLocalSpace
+ this.setIgnoreTransform(true);
+
+ // particles neither receive nor cast shadows
+ this.setShadowMode(ShadowMode.Off);
+
+ // particles are usually transparent
+ this.setQueueBucket(Bucket.Transparent);
+
+ meshType = type;
+
+ // Must create clone of shape/influencer so that a reference to a static is
+ // not maintained
+ shape = shape.deepClone();
+ particleInfluencer = particleInfluencer.clone();
+
+ control = new ParticleEmitterControl(this);
+ controls.add(control);
+
+ switch (meshType) {
+ case Point:
+ particleMesh = new ParticlePointMesh();
+ this.setMesh(particleMesh);
+ break;
+ case Triangle:
+ particleMesh = new ParticleTriMesh();
+ this.setMesh(particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ this.setNumParticles(numParticles);
+// particleMesh.initParticleData(this, particles.length);
+ }
+
+ /**
+ * For serialization only. Do not use.
+ */
+ public ParticleEmitter() {
+ super();
+ }
+
+ public void setShape(EmitterShape shape) {
+ this.shape = shape;
+ }
+
+ public EmitterShape getShape() {
+ return shape;
+ }
+
+ /**
+ * Set the {@link ParticleInfluencer} to influence this particle emitter.
+ *
+ * @param particleInfluencer the {@link ParticleInfluencer} to influence
+ * this particle emitter.
+ *
+ * @see ParticleInfluencer
+ */
+ public void setParticleInfluencer(ParticleInfluencer particleInfluencer) {
+ this.particleInfluencer = particleInfluencer;
+ }
+
+ /**
+ * Returns the {@link ParticleInfluencer} that influences this
+ * particle emitter.
+ *
+ * @return the {@link ParticleInfluencer} that influences this
+ * particle emitter.
+ *
+ * @see ParticleInfluencer
+ */
+ public ParticleInfluencer getParticleInfluencer() {
+ return particleInfluencer;
+ }
+
+ /**
+ * Returns the mesh type used by the particle emitter.
+ *
+ *
+ * @return the mesh type used by the particle emitter.
+ *
+ * @see #setMeshType(com.jme3.effect.ParticleMesh.Type)
+ * @see ParticleEmitter#ParticleEmitter(java.lang.String, com.jme3.effect.ParticleMesh.Type, int)
+ */
+ public ParticleMesh.Type getMeshType() {
+ return meshType;
+ }
+
+ /**
+ * Sets the type of mesh used by the particle emitter.
+ * @param meshType The mesh type to use
+ */
+ public void setMeshType(ParticleMesh.Type meshType) {
+ this.meshType = meshType;
+ switch (meshType) {
+ case Point:
+ particleMesh = new ParticlePointMesh();
+ this.setMesh(particleMesh);
+ break;
+ case Triangle:
+ particleMesh = new ParticleTriMesh();
+ this.setMesh(particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ this.setNumParticles(particles.length);
+ }
+
+ /**
+ * Returns true if particles should spawn in world space.
+ *
+ * @return true if particles should spawn in world space.
+ *
+ * @see ParticleEmitter#setInWorldSpace(boolean)
+ */
+ public boolean isInWorldSpace() {
+ return worldSpace;
+ }
+
+ /**
+ * Set to true if particles should spawn in world space.
+ *
+ * <p>If set to true and the particle emitter is moved in the scene,
+ * then particles that have already spawned won't be effected by this
+ * motion. If set to false, the particles will emit in local space
+ * and when the emitter is moved, so are all the particles that
+ * were emitted previously.
+ *
+ * @param worldSpace true if particles should spawn in world space.
+ */
+ public void setInWorldSpace(boolean worldSpace) {
+ this.setIgnoreTransform(worldSpace);
+ this.worldSpace = worldSpace;
+ }
+
+ /**
+ * Returns the number of visible particles (spawned but not dead).
+ *
+ * @return the number of visible particles
+ */
+ public int getNumVisibleParticles() {
+// return unusedIndices.size() + next;
+ return lastUsed + 1;
+ }
+
+ /**
+ * Set the maximum amount of particles that
+ * can exist at the same time with this emitter.
+ * Calling this method many times is not recommended.
+ *
+ * @param numParticles the maximum amount of particles that
+ * can exist at the same time with this emitter.
+ */
+ public final void setNumParticles(int numParticles) {
+ particles = new Particle[numParticles];
+ for (int i = 0; i < numParticles; i++) {
+ particles[i] = new Particle();
+ }
+ //We have to reinit the mesh's buffers with the new size
+ particleMesh.initParticleData(this, particles.length);
+ particleMesh.setImagesXY(this.imagesX, this.imagesY);
+ firstUnUsed = 0;
+ lastUsed = -1;
+ }
+
+ public int getMaxNumParticles() {
+ return particles.length;
+ }
+
+ /**
+ * Returns a list of all particles (shouldn't be used in most cases).
+ *
+ * <p>
+ * This includes both existing and non-existing particles.
+ * The size of the array is set to the <code>numParticles</code> value
+ * specified in the constructor or {@link ParticleEmitter#setNumParticles(int) }
+ * method.
+ *
+ * @return a list of all particles.
+ */
+ public Particle[] getParticles() {
+ return particles;
+ }
+
+ /**
+ * Get the normal which particles are facing.
+ *
+ * @return the normal which particles are facing.
+ *
+ * @see ParticleEmitter#setFaceNormal(com.jme3.math.Vector3f)
+ */
+ public Vector3f getFaceNormal() {
+ if (Vector3f.isValidVector(faceNormal)) {
+ return faceNormal;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sets the normal which particles are facing.
+ *
+ * <p>By default, particles
+ * will face the camera, but for some effects (e.g shockwave) it may
+ * be necessary to face a specific direction instead. To restore
+ * normal functionality, provide <code>null</code> as the argument for
+ * <code>faceNormal</code>.
+ *
+ * @param faceNormal The normals particles should face, or <code>null</code>
+ * if particles should face the camera.
+ */
+ public void setFaceNormal(Vector3f faceNormal) {
+ if (faceNormal == null || !Vector3f.isValidVector(faceNormal)) {
+ this.faceNormal.set(Vector3f.NAN);
+ } else {
+ this.faceNormal = faceNormal;
+ }
+ }
+
+ /**
+ * Returns the rotation speed in radians/sec for particles.
+ *
+ * @return the rotation speed in radians/sec for particles.
+ *
+ * @see ParticleEmitter#setRotateSpeed(float)
+ */
+ public float getRotateSpeed() {
+ return rotateSpeed;
+ }
+
+ /**
+ * Set the rotation speed in radians/sec for particles
+ * spawned after the invocation of this method.
+ *
+ * @param rotateSpeed the rotation speed in radians/sec for particles
+ * spawned after the invocation of this method.
+ */
+ public void setRotateSpeed(float rotateSpeed) {
+ this.rotateSpeed = rotateSpeed;
+ }
+
+ /**
+ * Returns true if every particle spawned
+ * should have a random facing angle.
+ *
+ * @return true if every particle spawned
+ * should have a random facing angle.
+ *
+ * @see ParticleEmitter#setRandomAngle(boolean)
+ */
+ public boolean isRandomAngle() {
+ return randomAngle;
+ }
+
+ /**
+ * Set to true if every particle spawned
+ * should have a random facing angle.
+ *
+ * @param randomAngle if every particle spawned
+ * should have a random facing angle.
+ */
+ public void setRandomAngle(boolean randomAngle) {
+ this.randomAngle = randomAngle;
+ }
+
+ /**
+ * Returns true if every particle spawned should get a random
+ * image.
+ *
+ * @return True if every particle spawned should get a random
+ * image.
+ *
+ * @see ParticleEmitter#setSelectRandomImage(boolean)
+ */
+ public boolean isSelectRandomImage() {
+ return selectRandomImage;
+ }
+
+ /**
+ * Set to true if every particle spawned
+ * should get a random image from a pool of images constructed from
+ * the texture, with X by Y possible images.
+ *
+ * <p>By default, X and Y are equal
+ * to 1, thus allowing only 1 possible image to be selected, but if the
+ * particle is configured with multiple images by using {@link ParticleEmitter#setImagesX(int) }
+ * and {#link ParticleEmitter#setImagesY(int) } methods, then multiple images
+ * can be selected. Setting to false will cause each particle to have an animation
+ * of images displayed, starting at image 1, and going until image X*Y when
+ * the particle reaches its end of life.
+ *
+ * @param selectRandomImage True if every particle spawned should get a random
+ * image.
+ */
+ public void setSelectRandomImage(boolean selectRandomImage) {
+ this.selectRandomImage = selectRandomImage;
+ }
+
+ /**
+ * Check if particles spawned should face their velocity.
+ *
+ * @return True if particles spawned should face their velocity.
+ *
+ * @see ParticleEmitter#setFacingVelocity(boolean)
+ */
+ public boolean isFacingVelocity() {
+ return facingVelocity;
+ }
+
+ /**
+ * Set to true if particles spawned should face
+ * their velocity (or direction to which they are moving towards).
+ *
+ * <p>This is typically used for e.g spark effects.
+ *
+ * @param followVelocity True if particles spawned should face their velocity.
+ *
+ */
+ public void setFacingVelocity(boolean followVelocity) {
+ this.facingVelocity = followVelocity;
+ }
+
+ /**
+ * Get the end color of the particles spawned.
+ *
+ * @return the end color of the particles spawned.
+ *
+ * @see ParticleEmitter#setEndColor(com.jme3.math.ColorRGBA)
+ */
+ public ColorRGBA getEndColor() {
+ return endColor;
+ }
+
+ /**
+ * Set the end color of the particles spawned.
+ *
+ * <p>The
+ * particle color at any time is determined by blending the start color
+ * and end color based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param endColor the end color of the particles spawned.
+ */
+ public void setEndColor(ColorRGBA endColor) {
+ this.endColor.set(endColor);
+ }
+
+ /**
+ * Get the end size of the particles spawned.
+ *
+ * @return the end size of the particles spawned.
+ *
+ * @see ParticleEmitter#setEndSize(float)
+ */
+ public float getEndSize() {
+ return endSize;
+ }
+
+ /**
+ * Set the end size of the particles spawned.
+ *
+ * <p>The
+ * particle size at any time is determined by blending the start size
+ * and end size based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param endSize the end size of the particles spawned.
+ */
+ public void setEndSize(float endSize) {
+ this.endSize = endSize;
+ }
+
+ /**
+ * Get the gravity vector.
+ *
+ * @return the gravity vector.
+ *
+ * @see ParticleEmitter#setGravity(com.jme3.math.Vector3f)
+ */
+ public Vector3f getGravity() {
+ return gravity;
+ }
+
+ /**
+ * This method sets the gravity vector.
+ *
+ * @param gravity the gravity vector
+ */
+ public void setGravity(Vector3f gravity) {
+ this.gravity.set(gravity);
+ }
+
+ /**
+ * Sets the gravity vector.
+ *
+ * @param x the x component of the gravity vector
+ * @param y the y component of the gravity vector
+ * @param z the z component of the gravity vector
+ */
+ public void setGravity(float x, float y, float z) {
+ this.gravity.x = x;
+ this.gravity.y = y;
+ this.gravity.z = z;
+ }
+
+ /**
+ * Get the high value of life.
+ *
+ * @return the high value of life.
+ *
+ * @see ParticleEmitter#setHighLife(float)
+ */
+ public float getHighLife() {
+ return highLife;
+ }
+
+ /**
+ * Set the high value of life.
+ *
+ * <p>The particle's lifetime/expiration
+ * is determined by randomly selecting a time between low life and high life.
+ *
+ * @param highLife the high value of life.
+ */
+ public void setHighLife(float highLife) {
+ this.highLife = highLife;
+ }
+
+ /**
+ * Get the number of images along the X axis (width).
+ *
+ * @return the number of images along the X axis (width).
+ *
+ * @see ParticleEmitter#setImagesX(int)
+ */
+ public int getImagesX() {
+ return imagesX;
+ }
+
+ /**
+ * Set the number of images along the X axis (width).
+ *
+ * <p>To determine
+ * how multiple particle images are selected and used, see the
+ * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
+ *
+ * @param imagesX the number of images along the X axis (width).
+ */
+ public void setImagesX(int imagesX) {
+ this.imagesX = imagesX;
+ particleMesh.setImagesXY(this.imagesX, this.imagesY);
+ }
+
+ /**
+ * Get the number of images along the Y axis (height).
+ *
+ * @return the number of images along the Y axis (height).
+ *
+ * @see ParticleEmitter#setImagesY(int)
+ */
+ public int getImagesY() {
+ return imagesY;
+ }
+
+ /**
+ * Set the number of images along the Y axis (height).
+ *
+ * <p>To determine how multiple particle images are selected and used, see the
+ * {@link ParticleEmitter#setSelectRandomImage(boolean) } method.
+ *
+ * @param imagesY the number of images along the Y axis (height).
+ */
+ public void setImagesY(int imagesY) {
+ this.imagesY = imagesY;
+ particleMesh.setImagesXY(this.imagesX, this.imagesY);
+ }
+
+ /**
+ * Get the low value of life.
+ *
+ * @return the low value of life.
+ *
+ * @see ParticleEmitter#setLowLife(float)
+ */
+ public float getLowLife() {
+ return lowLife;
+ }
+
+ /**
+ * Set the low value of life.
+ *
+ * <p>The particle's lifetime/expiration
+ * is determined by randomly selecting a time between low life and high life.
+ *
+ * @param lowLife the low value of life.
+ */
+ public void setLowLife(float lowLife) {
+ this.lowLife = lowLife;
+ }
+
+ /**
+ * Get the number of particles to spawn per
+ * second.
+ *
+ * @return the number of particles to spawn per
+ * second.
+ *
+ * @see ParticleEmitter#setParticlesPerSec(float)
+ */
+ public float getParticlesPerSec() {
+ return particlesPerSec;
+ }
+
+ /**
+ * Set the number of particles to spawn per
+ * second.
+ *
+ * @param particlesPerSec the number of particles to spawn per
+ * second.
+ */
+ public void setParticlesPerSec(float particlesPerSec) {
+ this.particlesPerSec = particlesPerSec;
+ }
+
+ /**
+ * Get the start color of the particles spawned.
+ *
+ * @return the start color of the particles spawned.
+ *
+ * @see ParticleEmitter#setStartColor(com.jme3.math.ColorRGBA)
+ */
+ public ColorRGBA getStartColor() {
+ return startColor;
+ }
+
+ /**
+ * Set the start color of the particles spawned.
+ *
+ * <p>The particle color at any time is determined by blending the start color
+ * and end color based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param startColor the start color of the particles spawned
+ */
+ public void setStartColor(ColorRGBA startColor) {
+ this.startColor.set(startColor);
+ }
+
+ /**
+ * Get the start color of the particles spawned.
+ *
+ * @return the start color of the particles spawned.
+ *
+ * @see ParticleEmitter#setStartSize(float)
+ */
+ public float getStartSize() {
+ return startSize;
+ }
+
+ /**
+ * Set the start size of the particles spawned.
+ *
+ * <p>The particle size at any time is determined by blending the start size
+ * and end size based on the particle's current time of life relative
+ * to its end of life.
+ *
+ * @param startSize the start size of the particles spawned.
+ */
+ public void setStartSize(float startSize) {
+ this.startSize = startSize;
+ }
+
+ /**
+ * @deprecated Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead.
+ */
+ @Deprecated
+ public Vector3f getInitialVelocity() {
+ return particleInfluencer.getInitialVelocity();
+ }
+
+ /**
+ * @param initialVelocity Set the initial velocity a particle is spawned with,
+ * the initial velocity given in the parameter will be varied according
+ * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }.
+ * A particle will move toward its velocity unless it is effected by the
+ * gravity.
+ *
+ * @deprecated
+ * This method is deprecated.
+ * Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead.
+ *
+ * @see ParticleEmitter#setVelocityVariation(float)
+ * @see ParticleEmitter#setGravity(float)
+ */
+ @Deprecated
+ public void setInitialVelocity(Vector3f initialVelocity) {
+ this.particleInfluencer.setInitialVelocity(initialVelocity);
+ }
+
+ /**
+ * @deprecated
+ * This method is deprecated.
+ * Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead.
+ * @return the initial velocity variation factor
+ */
+ @Deprecated
+ public float getVelocityVariation() {
+ return particleInfluencer.getVelocityVariation();
+ }
+
+ /**
+ * @param variation Set the variation by which the initial velocity
+ * of the particle is determined. <code>variation</code> should be a value
+ * from 0 to 1, where 0 means particles are to spawn with exactly
+ * the velocity given in {@link ParticleEmitter#setStartVel(com.jme3.math.Vector3f) },
+ * and 1 means particles are to spawn with a completely random velocity.
+ *
+ * @deprecated
+ * This method is deprecated.
+ * Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead.
+ */
+ @Deprecated
+ public void setVelocityVariation(float variation) {
+ this.particleInfluencer.setVelocityVariation(variation);
+ }
+
+ private Particle emitParticle(Vector3f min, Vector3f max) {
+ int idx = lastUsed + 1;
+ if (idx >= particles.length) {
+ return null;
+ }
+
+ Particle p = particles[idx];
+ if (selectRandomImage) {
+ p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) * imagesX + FastMath.nextRandomInt(0, imagesX - 1);
+ }
+
+ p.startlife = lowLife + FastMath.nextRandomFloat() * (highLife - lowLife);
+ p.life = p.startlife;
+ p.color.set(startColor);
+ p.size = startSize;
+ //shape.getRandomPoint(p.position);
+ particleInfluencer.influenceParticle(p, shape);
+ if (worldSpace) {
+ worldTransform.transformVector(p.position, p.position);
+ worldTransform.getRotation().mult(p.velocity, p.velocity);
+ // TODO: Make scale relevant somehow??
+ }
+ if (randomAngle) {
+ p.angle = FastMath.nextRandomFloat() * FastMath.TWO_PI;
+ }
+ if (rotateSpeed != 0) {
+ p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f);
+ }
+
+ temp.set(p.position).addLocal(p.size, p.size, p.size);
+ max.maxLocal(temp);
+ temp.set(p.position).subtractLocal(p.size, p.size, p.size);
+ min.minLocal(temp);
+
+ ++lastUsed;
+ firstUnUsed = idx + 1;
+ return p;
+ }
+
+ /**
+ * Instantly emits all the particles possible to be emitted. Any particles
+ * which are currently inactive will be spawned immediately.
+ */
+ public void emitAllParticles() {
+ // Force world transform to update
+ this.getWorldTransform();
+
+ TempVars vars = TempVars.get();
+
+ BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
+
+ Vector3f min = vars.vect1;
+ Vector3f max = vars.vect2;
+
+ bbox.getMin(min);
+ bbox.getMax(max);
+
+ if (!Vector3f.isValidVector(min)) {
+ min.set(Vector3f.POSITIVE_INFINITY);
+ }
+ if (!Vector3f.isValidVector(max)) {
+ max.set(Vector3f.NEGATIVE_INFINITY);
+ }
+
+ while (emitParticle(min, max) != null);
+
+ bbox.setMinMax(min, max);
+ this.setBoundRefresh();
+
+ vars.release();
+ }
+
+ /**
+ * Instantly kills all active particles, after this method is called, all
+ * particles will be dead and no longer visible.
+ */
+ public void killAllParticles() {
+ for (int i = 0; i < particles.length; ++i) {
+ if (particles[i].life > 0) {
+ this.freeParticle(i);
+ }
+ }
+ }
+
+ /**
+ * Kills the particle at the given index.
+ *
+ * @param index The index of the particle to kill
+ * @see #getParticles()
+ */
+ public void killParticle(int index){
+ freeParticle(index);
+ }
+
+ private void freeParticle(int idx) {
+ Particle p = particles[idx];
+ p.life = 0;
+ p.size = 0f;
+ p.color.set(0, 0, 0, 0);
+ p.imageIndex = 0;
+ p.angle = 0;
+ p.rotateSpeed = 0;
+
+ if (idx == lastUsed) {
+ while (lastUsed >= 0 && particles[lastUsed].life == 0) {
+ lastUsed--;
+ }
+ }
+ if (idx < firstUnUsed) {
+ firstUnUsed = idx;
+ }
+ }
+
+ private void swap(int idx1, int idx2) {
+ Particle p1 = particles[idx1];
+ particles[idx1] = particles[idx2];
+ particles[idx2] = p1;
+ }
+
+ private void updateParticle(Particle p, float tpf, Vector3f min, Vector3f max){
+ // applying gravity
+ p.velocity.x -= gravity.x * tpf;
+ p.velocity.y -= gravity.y * tpf;
+ p.velocity.z -= gravity.z * tpf;
+ temp.set(p.velocity).multLocal(tpf);
+ p.position.addLocal(temp);
+
+ // affecting color, size and angle
+ float b = (p.startlife - p.life) / p.startlife;
+ p.color.interpolate(startColor, endColor, b);
+ p.size = FastMath.interpolateLinear(b, startSize, endSize);
+ p.angle += p.rotateSpeed * tpf;
+
+ // Computing bounding volume
+ temp.set(p.position).addLocal(p.size, p.size, p.size);
+ max.maxLocal(temp);
+ temp.set(p.position).subtractLocal(p.size, p.size, p.size);
+ min.minLocal(temp);
+
+ if (!selectRandomImage) {
+ p.imageIndex = (int) (b * imagesX * imagesY);
+ }
+ }
+
+ private void updateParticleState(float tpf) {
+ // Force world transform to update
+ this.getWorldTransform();
+
+ TempVars vars = TempVars.get();
+
+ Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY);
+ Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY);
+
+ for (int i = 0; i < particles.length; ++i) {
+ Particle p = particles[i];
+ if (p.life == 0) { // particle is dead
+// assert i <= firstUnUsed;
+ continue;
+ }
+
+ p.life -= tpf;
+ if (p.life <= 0) {
+ this.freeParticle(i);
+ continue;
+ }
+
+ updateParticle(p, tpf, min, max);
+
+ if (firstUnUsed < i) {
+ this.swap(firstUnUsed, i);
+ if (i == lastUsed) {
+ lastUsed = firstUnUsed;
+ }
+ firstUnUsed++;
+ }
+ }
+
+ // Spawns particles within the tpf timeslot with proper age
+ float interval = 1f / particlesPerSec;
+ tpf += timeDifference;
+ while (tpf > interval){
+ tpf -= interval;
+ Particle p = emitParticle(min, max);
+ if (p != null){
+ p.life -= tpf;
+ if (p.life <= 0){
+ freeParticle(lastUsed);
+ }else{
+ updateParticle(p, tpf, min, max);
+ }
+ }
+ }
+ timeDifference = tpf;
+
+ BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
+ bbox.setMinMax(min, max);
+ this.setBoundRefresh();
+
+ vars.release();
+ }
+
+ /**
+ * Set to enable or disable the particle emitter
+ *
+ * <p>When a particle is
+ * disabled, it will be "frozen in time" and not update.
+ *
+ * @param enabled True to enable the particle emitter
+ */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Check if a particle emitter is enabled for update.
+ *
+ * @return True if a particle emitter is enabled for update.
+ *
+ * @see ParticleEmitter#setEnabled(boolean)
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * Callback from Control.update(), do not use.
+ * @param tpf
+ */
+ public void updateFromControl(float tpf) {
+ if (enabled) {
+ this.updateParticleState(tpf);
+ }
+ }
+
+ /**
+ * Callback from Control.render(), do not use.
+ *
+ * @param rm
+ * @param vp
+ */
+ private void renderFromControl(RenderManager rm, ViewPort vp) {
+ Camera cam = vp.getCamera();
+
+ if (meshType == ParticleMesh.Type.Point) {
+ float C = cam.getProjectionMatrix().m00;
+ C *= cam.getWidth() * 0.5f;
+
+ // send attenuation params
+ this.getMaterial().setFloat("Quadratic", C);
+ }
+
+ Matrix3f inverseRotation = Matrix3f.IDENTITY;
+ TempVars vars = null;
+ if (!worldSpace) {
+ vars = TempVars.get();
+
+ inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
+ }
+ particleMesh.updateParticleData(particles, cam, inverseRotation);
+ if (!worldSpace) {
+ vars.release();
+ }
+ }
+
+ public void preload(RenderManager rm, ViewPort vp) {
+ this.updateParticleState(0);
+ particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
+ }
+
+ @Override
+ public void write(JmeExporter ex) throws IOException {
+ super.write(ex);
+ OutputCapsule oc = ex.getCapsule(this);
+ oc.write(shape, "shape", DEFAULT_SHAPE);
+ oc.write(meshType, "meshType", ParticleMesh.Type.Triangle);
+ oc.write(enabled, "enabled", true);
+ oc.write(particles.length, "numParticles", 0);
+ oc.write(particlesPerSec, "particlesPerSec", 0);
+ oc.write(lowLife, "lowLife", 0);
+ oc.write(highLife, "highLife", 0);
+ oc.write(gravity, "gravity", null);
+ oc.write(imagesX, "imagesX", 1);
+ oc.write(imagesY, "imagesY", 1);
+
+ oc.write(startColor, "startColor", null);
+ oc.write(endColor, "endColor", null);
+ oc.write(startSize, "startSize", 0);
+ oc.write(endSize, "endSize", 0);
+ oc.write(worldSpace, "worldSpace", false);
+ oc.write(facingVelocity, "facingVelocity", false);
+ oc.write(faceNormal, "faceNormal", new Vector3f(Vector3f.NAN));
+ oc.write(selectRandomImage, "selectRandomImage", false);
+ oc.write(randomAngle, "randomAngle", false);
+ oc.write(rotateSpeed, "rotateSpeed", 0);
+
+ oc.write(particleInfluencer, "influencer", DEFAULT_INFLUENCER);
+ }
+
+ @Override
+ public void read(JmeImporter im) throws IOException {
+ super.read(im);
+ InputCapsule ic = im.getCapsule(this);
+ shape = (EmitterShape) ic.readSavable("shape", DEFAULT_SHAPE);
+
+ if (shape == DEFAULT_SHAPE) {
+ // Prevent reference to static
+ shape = shape.deepClone();
+ }
+
+ meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle);
+ int numParticles = ic.readInt("numParticles", 0);
+
+
+ enabled = ic.readBoolean("enabled", true);
+ particlesPerSec = ic.readFloat("particlesPerSec", 0);
+ lowLife = ic.readFloat("lowLife", 0);
+ highLife = ic.readFloat("highLife", 0);
+ gravity = (Vector3f) ic.readSavable("gravity", null);
+ imagesX = ic.readInt("imagesX", 1);
+ imagesY = ic.readInt("imagesY", 1);
+
+ startColor = (ColorRGBA) ic.readSavable("startColor", null);
+ endColor = (ColorRGBA) ic.readSavable("endColor", null);
+ startSize = ic.readFloat("startSize", 0);
+ endSize = ic.readFloat("endSize", 0);
+ worldSpace = ic.readBoolean("worldSpace", false);
+ this.setIgnoreTransform(worldSpace);
+ facingVelocity = ic.readBoolean("facingVelocity", false);
+ faceNormal = (Vector3f)ic.readSavable("faceNormal", new Vector3f(Vector3f.NAN));
+ selectRandomImage = ic.readBoolean("selectRandomImage", false);
+ randomAngle = ic.readBoolean("randomAngle", false);
+ rotateSpeed = ic.readFloat("rotateSpeed", 0);
+
+ switch (meshType) {
+ case Point:
+ particleMesh = new ParticlePointMesh();
+ this.setMesh(particleMesh);
+ break;
+ case Triangle:
+ particleMesh = new ParticleTriMesh();
+ this.setMesh(particleMesh);
+ break;
+ default:
+ throw new IllegalStateException("Unrecognized particle type: " + meshType);
+ }
+ this.setNumParticles(numParticles);
+// particleMesh.initParticleData(this, particles.length);
+// particleMesh.setImagesXY(imagesX, imagesY);
+
+ particleInfluencer = (ParticleInfluencer) ic.readSavable("influencer", DEFAULT_INFLUENCER);
+ if (particleInfluencer == DEFAULT_INFLUENCER) {
+ particleInfluencer = particleInfluencer.clone();
+ }
+
+ if (im.getFormatVersion() == 0) {
+ // compatibility before the control inside particle emitter
+ // was changed:
+ // find it in the controls and take it out, then add the proper one in
+ for (int i = 0; i < controls.size(); i++) {
+ Object obj = controls.get(i);
+ if (obj instanceof ParticleEmitter) {
+ controls.remove(i);
+ // now add the proper one in
+ controls.add(new ParticleEmitterControl(this));
+ break;
+ }
+ }
+
+ // compatability before gravity was not a vector but a float
+ if (gravity == null) {
+ gravity = new Vector3f();
+ gravity.y = ic.readFloat("gravity", 0);
+ }
+ } else {
+ // since the parentEmitter is not loaded, it must be
+ // loaded separately
+ control = getControl(ParticleEmitterControl.class);
+ control.parentEmitter = this;
+
+ }
+ }
+}