diff options
Diffstat (limited to 'engine/src/core/com/jme3/effect/ParticleEmitter.java')
-rw-r--r-- | engine/src/core/com/jme3/effect/ParticleEmitter.java | 1206 |
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; + + } + } +} |