diff options
Diffstat (limited to 'engine/src/core/com/jme3/effect')
18 files changed, 2995 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/effect/Particle.java b/engine/src/core/com/jme3/effect/Particle.java new file mode 100644 index 0000000..8e976f9 --- /dev/null +++ b/engine/src/core/com/jme3/effect/Particle.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2010 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.math.ColorRGBA; +import com.jme3.math.Vector3f; + +/** + * Represents a single particle in a {@link ParticleEmitter}. + * + * @author Kirill Vainer + */ +public class Particle { + + /** + * Particle velocity. + */ + public final Vector3f velocity = new Vector3f(); + + /** + * Current particle position + */ + public final Vector3f position = new Vector3f(); + + /** + * Particle color + */ + public final ColorRGBA color = new ColorRGBA(0,0,0,0); + + /** + * Particle size or radius. + */ + public float size; + + /** + * Particle remaining life, in seconds. + */ + public float life; + + /** + * The initial particle life + */ + public float startlife; + + /** + * Particle rotation angle (in radians). + */ + public float angle; + + /** + * Particle rotation angle speed (in radians). + */ + public float rotateSpeed; + + /** + * Particle image index. + */ + public int imageIndex = 0; + + /** + * Distance to camera. Only used for sorted particles. + */ + //public float distToCam; +} diff --git a/engine/src/core/com/jme3/effect/ParticleComparator.java b/engine/src/core/com/jme3/effect/ParticleComparator.java new file mode 100644 index 0000000..dbe52dd --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticleComparator.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2009-2010 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.renderer.Camera; +import java.util.Comparator; + +@Deprecated +class ParticleComparator implements Comparator<Particle> { + + private Camera cam; + + public void setCamera(Camera cam){ + this.cam = cam; + } + + public int compare(Particle p1, Particle p2) { + return 0; // unused + /* + if (p1.life <= 0 || p2.life <= 0) + return 0; + +// if (p1.life <= 0) +// return 1; +// else if (p2.life <= 0) +// return -1; + + float d1 = p1.distToCam, d2 = p2.distToCam; + + if (d1 == -1){ + d1 = cam.distanceToNearPlane(p1.position); + p1.distToCam = d1; + } + if (d2 == -1){ + d2 = cam.distanceToNearPlane(p2.position); + p2.distToCam = d2; + } + + if (d1 < d2) + return 1; + else if (d1 > d2) + return -1; + else + return 0; + */ + } +}
\ No newline at end of file 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; + + } + } +} diff --git a/engine/src/core/com/jme3/effect/ParticleMesh.java b/engine/src/core/com/jme3/effect/ParticleMesh.java new file mode 100644 index 0000000..adace45 --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticleMesh.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2010 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.material.RenderState; +import com.jme3.math.Matrix3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Mesh; + +/** + * The <code>ParticleMesh</code> is the underlying visual implementation of a + * {@link ParticleEmitter particle emitter}. + * + * @author Kirill Vainer + */ +public abstract class ParticleMesh extends Mesh { + + /** + * Type of particle mesh + */ + public enum Type { + /** + * The particle mesh is composed of points. Each particle is a point. + * This can be used in conjuction with {@link RenderState#setPointSprite(boolean) point sprites} + * to render particles the usual way. + */ + Point, + + /** + * The particle mesh is composed of triangles. Each particle is + * two triangles making a single quad. + */ + Triangle; + } + + /** + * Initialize mesh data. + * + * @param emitter The emitter which will use this <code>ParticleMesh</code>. + * @param numParticles The maxmimum number of particles to simulate + */ + public abstract void initParticleData(ParticleEmitter emitter, int numParticles); + + /** + * Set the images on the X and Y coordinates + * @param imagesX Images on the X coordinate + * @param imagesY Images on the Y coordinate + */ + public abstract void setImagesXY(int imagesX, int imagesY); + + /** + * Update the particle visual data. Typically called every frame. + */ + public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation); + +} diff --git a/engine/src/core/com/jme3/effect/ParticlePointMesh.java b/engine/src/core/com/jme3/effect/ParticlePointMesh.java new file mode 100644 index 0000000..28db3e6 --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticlePointMesh.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2009-2010 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.math.Matrix3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +public class ParticlePointMesh extends ParticleMesh { + + private ParticleEmitter emitter; + + private int imagesX = 1; + private int imagesY = 1; + + @Override + public void setImagesXY(int imagesX, int imagesY) { + this.imagesX = imagesX; + this.imagesY = imagesY; + } + + @Override + public void initParticleData(ParticleEmitter emitter, int numParticles) { + setMode(Mode.Points); + + this.emitter = emitter; + + // set positions + FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles); + VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position); + pvb.setupData(Usage.Stream, 3, Format.Float, pb); + + //if the buffer is already set only update the data + VertexBuffer buf = getBuffer(VertexBuffer.Type.Position); + if (buf != null) { + buf.updateData(pb); + } else { + setBuffer(pvb); + } + + // set colors + ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4); + VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color); + cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb); + cvb.setNormalized(true); + + buf = getBuffer(VertexBuffer.Type.Color); + if (buf != null) { + buf.updateData(cb); + } else { + setBuffer(cvb); + } + + // set sizes + FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles); + VertexBuffer svb = new VertexBuffer(VertexBuffer.Type.Size); + svb.setupData(Usage.Stream, 1, Format.Float, sb); + + buf = getBuffer(VertexBuffer.Type.Size); + if (buf != null) { + buf.updateData(sb); + } else { + setBuffer(svb); + } + + // set UV-scale + FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles*4); + VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord); + tvb.setupData(Usage.Stream, 4, Format.Float, tb); + + buf = getBuffer(VertexBuffer.Type.TexCoord); + if (buf != null) { + buf.updateData(tb); + } else { + setBuffer(tvb); + } + } + + @Override + public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) { + VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position); + FloatBuffer positions = (FloatBuffer) pvb.getData(); + + VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color); + ByteBuffer colors = (ByteBuffer) cvb.getData(); + + VertexBuffer svb = getBuffer(VertexBuffer.Type.Size); + FloatBuffer sizes = (FloatBuffer) svb.getData(); + + VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord); + FloatBuffer texcoords = (FloatBuffer) tvb.getData(); + + float sizeScale = emitter.getWorldScale().x; + + // update data in vertex buffers + positions.rewind(); + colors.rewind(); + sizes.rewind(); + texcoords.rewind(); + for (int i = 0; i < particles.length; i++){ + Particle p = particles[i]; + + positions.put(p.position.x) + .put(p.position.y) + .put(p.position.z); + + sizes.put(p.size * sizeScale); + colors.putInt(p.color.asIntABGR()); + + int imgX = p.imageIndex % imagesX; + int imgY = (p.imageIndex - imgX) / imagesY; + + float startX = ((float) imgX) / imagesX; + float startY = ((float) imgY) / imagesY; + float endX = startX + (1f / imagesX); + float endY = startY + (1f / imagesY); + + texcoords.put(startX).put(startY).put(endX).put(endY); + } + positions.flip(); + colors.flip(); + sizes.flip(); + texcoords.flip(); + + // force renderer to re-send data to GPU + pvb.updateData(positions); + cvb.updateData(colors); + svb.updateData(sizes); + tvb.updateData(texcoords); + } +} diff --git a/engine/src/core/com/jme3/effect/ParticleTriMesh.java b/engine/src/core/com/jme3/effect/ParticleTriMesh.java new file mode 100644 index 0000000..8d27838 --- /dev/null +++ b/engine/src/core/com/jme3/effect/ParticleTriMesh.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2009-2010 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.math.FastMath; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.VertexBuffer.Format; +import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.util.BufferUtils; +import com.jme3.util.SortUtil; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +public class ParticleTriMesh extends ParticleMesh { + + private int imagesX = 1; + private int imagesY = 1; + private boolean uniqueTexCoords = false; + private ParticleComparator comparator = new ParticleComparator(); + private ParticleEmitter emitter; + private Particle[] particlesCopy; + + @Override + public void initParticleData(ParticleEmitter emitter, int numParticles) { + setMode(Mode.Triangles); + + this.emitter = emitter; + + particlesCopy = new Particle[numParticles]; + + // set positions + FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles * 4); + VertexBuffer pvb = new VertexBuffer(VertexBuffer.Type.Position); + pvb.setupData(Usage.Stream, 3, Format.Float, pb); + + //if the buffer is already set only update the data + VertexBuffer buf = getBuffer(VertexBuffer.Type.Position); + if (buf != null) { + buf.updateData(pb); + } else { + setBuffer(pvb); + } + + // set colors + ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4 * 4); + VertexBuffer cvb = new VertexBuffer(VertexBuffer.Type.Color); + cvb.setupData(Usage.Stream, 4, Format.UnsignedByte, cb); + cvb.setNormalized(true); + + buf = getBuffer(VertexBuffer.Type.Color); + if (buf != null) { + buf.updateData(cb); + } else { + setBuffer(cvb); + } + + // set texcoords + VertexBuffer tvb = new VertexBuffer(VertexBuffer.Type.TexCoord); + FloatBuffer tb = BufferUtils.createVector2Buffer(numParticles * 4); + + uniqueTexCoords = false; + for (int i = 0; i < numParticles; i++){ + tb.put(0f).put(1f); + tb.put(1f).put(1f); + tb.put(0f).put(0f); + tb.put(1f).put(0f); + } + tb.flip(); + tvb.setupData(Usage.Static, 2, Format.Float, tb); + + buf = getBuffer(VertexBuffer.Type.TexCoord); + if (buf != null) { + buf.updateData(tb); + } else { + setBuffer(tvb); + } + + // set indices + ShortBuffer ib = BufferUtils.createShortBuffer(numParticles * 6); + for (int i = 0; i < numParticles; i++){ + int startIdx = (i * 4); + + // triangle 1 + ib.put((short)(startIdx + 1)) + .put((short)(startIdx + 0)) + .put((short)(startIdx + 2)); + + // triangle 2 + ib.put((short)(startIdx + 1)) + .put((short)(startIdx + 2)) + .put((short)(startIdx + 3)); + } + ib.flip(); + + VertexBuffer ivb = new VertexBuffer(VertexBuffer.Type.Index); + ivb.setupData(Usage.Static, 3, Format.UnsignedShort, ib); + + buf = getBuffer(VertexBuffer.Type.Index); + if (buf != null) { + buf.updateData(ib); + } else { + setBuffer(ivb); + } + + } + + @Override + public void setImagesXY(int imagesX, int imagesY) { + this.imagesX = imagesX; + this.imagesY = imagesY; + if (imagesX != 1 || imagesY != 1){ + uniqueTexCoords = true; + getBuffer(VertexBuffer.Type.TexCoord).setUsage(Usage.Stream); + } + } + + @Override + public void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation) { + System.arraycopy(particles, 0, particlesCopy, 0, particlesCopy.length); + comparator.setCamera(cam); +// Arrays.sort(particlesCopy, comparator); +// SortUtil.qsort(particlesCopy, comparator); + SortUtil.msort(particles, particlesCopy, comparator); + particles = particlesCopy; + + VertexBuffer pvb = getBuffer(VertexBuffer.Type.Position); + FloatBuffer positions = (FloatBuffer) pvb.getData(); + + VertexBuffer cvb = getBuffer(VertexBuffer.Type.Color); + ByteBuffer colors = (ByteBuffer) cvb.getData(); + + VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord); + FloatBuffer texcoords = (FloatBuffer) tvb.getData(); + + Vector3f camUp = cam.getUp(); + Vector3f camLeft = cam.getLeft(); + Vector3f camDir = cam.getDirection(); + + inverseRotation.multLocal(camUp); + inverseRotation.multLocal(camLeft); + inverseRotation.multLocal(camDir); + + boolean facingVelocity = emitter.isFacingVelocity(); + + Vector3f up = new Vector3f(), + left = new Vector3f(); + + if (!facingVelocity){ + up.set(camUp); + left.set(camLeft); + } + + // update data in vertex buffers + positions.clear(); + colors.clear(); + texcoords.clear(); + Vector3f faceNormal = emitter.getFaceNormal(); + + for (int i = 0; i < particles.length; i++){ + Particle p = particles[i]; + boolean dead = p.life == 0; + if (dead){ + positions.put(0).put(0).put(0); + positions.put(0).put(0).put(0); + positions.put(0).put(0).put(0); + positions.put(0).put(0).put(0); + continue; + } + + if (facingVelocity){ + left.set(p.velocity).normalizeLocal(); + camDir.cross(left, up); + up.multLocal(p.size); + left.multLocal(p.size); + }else if (faceNormal != null){ + up.set(faceNormal).crossLocal(Vector3f.UNIT_X); + faceNormal.cross(up, left); + up.multLocal(p.size); + left.multLocal(p.size); + }else if (p.angle != 0){ + float cos = FastMath.cos(p.angle) * p.size; + float sin = FastMath.sin(p.angle) * p.size; + + left.x = camLeft.x * cos + camUp.x * sin; + left.y = camLeft.y * cos + camUp.y * sin; + left.z = camLeft.z * cos + camUp.z * sin; + + up.x = camLeft.x * -sin + camUp.x * cos; + up.y = camLeft.y * -sin + camUp.y * cos; + up.z = camLeft.z * -sin + camUp.z * cos; + }else{ + up.set(camUp); + left.set(camLeft); + up.multLocal(p.size); + left.multLocal(p.size); + } + + positions.put(p.position.x + left.x + up.x) + .put(p.position.y + left.y + up.y) + .put(p.position.z + left.z + up.z); + + positions.put(p.position.x - left.x + up.x) + .put(p.position.y - left.y + up.y) + .put(p.position.z - left.z + up.z); + + positions.put(p.position.x + left.x - up.x) + .put(p.position.y + left.y - up.y) + .put(p.position.z + left.z - up.z); + + positions.put(p.position.x - left.x - up.x) + .put(p.position.y - left.y - up.y) + .put(p.position.z - left.z - up.z); + + if (uniqueTexCoords){ + int imgX = p.imageIndex % imagesX; + int imgY = (p.imageIndex - imgX) / imagesY; + + float startX = ((float) imgX) / imagesX; + float startY = ((float) imgY) / imagesY; + float endX = startX + (1f / imagesX); + float endY = startY + (1f / imagesY); + + texcoords.put(startX).put(endY); + texcoords.put(endX).put(endY); + texcoords.put(startX).put(startY); + texcoords.put(endX).put(startY); + } + + int abgr = p.color.asIntABGR(); + colors.putInt(abgr); + colors.putInt(abgr); + colors.putInt(abgr); + colors.putInt(abgr); + } + + positions.clear(); + colors.clear(); + if (!uniqueTexCoords) + texcoords.clear(); + else{ + texcoords.clear(); + tvb.updateData(texcoords); + } + + // force renderer to re-send data to GPU + pvb.updateData(positions); + cvb.updateData(colors); + } + +} diff --git a/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java new file mode 100644 index 0000000..80f52d9 --- /dev/null +++ b/engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java @@ -0,0 +1,92 @@ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +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.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * This emitter influences the particles so that they move all in the same direction. + * The direction may vary a little if the velocity variation is non zero. + * This influencer is default for the particle emitter. + * @author Marcin Roguski (Kaelthas) + */ +public class DefaultParticleInfluencer implements ParticleInfluencer { + + /** Temporary variable used to help with calculations. */ + protected transient Vector3f temp = new Vector3f(); + /** The initial velocity of the particles. */ + protected Vector3f startVelocity = new Vector3f(); + /** The velocity's variation of the particles. */ + protected float velocityVariation = 0.2f; + + @Override + public void influenceParticle(Particle particle, EmitterShape emitterShape) { + emitterShape.getRandomPoint(particle.position); + this.applyVelocityVariation(particle); + } + + /** + * This method applies the variation to the particle with already set velocity. + * @param particle + * the particle to be affected + */ + protected void applyVelocityVariation(Particle particle) { + particle.velocity.set(startVelocity); + temp.set(FastMath.nextRandomFloat(), FastMath.nextRandomFloat(), FastMath.nextRandomFloat()); + temp.multLocal(2f); + temp.subtractLocal(1f, 1f, 1f); + temp.multLocal(startVelocity.length()); + particle.velocity.interpolate(temp, velocityVariation); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(startVelocity, "startVelocity", Vector3f.ZERO); + oc.write(velocityVariation, "variation", 0.2f); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + startVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone()); + velocityVariation = ic.readFloat("variation", 0.2f); + } + + @Override + public ParticleInfluencer clone() { + try { + DefaultParticleInfluencer clone = (DefaultParticleInfluencer) super.clone(); + clone.startVelocity = startVelocity.clone(); + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public void setInitialVelocity(Vector3f initialVelocity) { + this.startVelocity.set(initialVelocity); + } + + @Override + public Vector3f getInitialVelocity() { + return startVelocity; + } + + @Override + public void setVelocityVariation(float variation) { + this.velocityVariation = variation; + } + + @Override + public float getVelocityVariation() { + return velocityVariation; + } +} diff --git a/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java new file mode 100644 index 0000000..013a5db --- /dev/null +++ b/engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java @@ -0,0 +1,55 @@ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.math.Vector3f; +import java.io.IOException; + +/** + * This influencer does not influence particle at all. + * It makes particles not to move. + * @author Marcin Roguski (Kaelthas) + */ +public class EmptyParticleInfluencer implements ParticleInfluencer { + + @Override + public void write(JmeExporter ex) throws IOException { + } + + @Override + public void read(JmeImporter im) throws IOException { + } + + @Override + public void influenceParticle(Particle particle, EmitterShape emitterShape) { + } + + @Override + public void setInitialVelocity(Vector3f initialVelocity) { + } + + @Override + public Vector3f getInitialVelocity() { + return null; + } + + @Override + public void setVelocityVariation(float variation) { + } + + @Override + public float getVelocityVariation() { + return 0; + } + + @Override + public ParticleInfluencer clone() { + try { + return (ParticleInfluencer) super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java new file mode 100644 index 0000000..a2701be --- /dev/null +++ b/engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java @@ -0,0 +1,142 @@ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +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.FastMath; +import com.jme3.math.Matrix3f; +import java.io.IOException; + +/** + * This influencer calculates initial velocity with the use of the emitter's shape. + * @author Marcin Roguski (Kaelthas) + */ +public class NewtonianParticleInfluencer extends DefaultParticleInfluencer { + + /** Normal to emitter's shape factor. */ + protected float normalVelocity; + /** Emitter's surface tangent factor. */ + protected float surfaceTangentFactor; + /** Emitters tangent rotation factor. */ + protected float surfaceTangentRotation; + + /** + * Constructor. Sets velocity variation to 0.0f. + */ + public NewtonianParticleInfluencer() { + this.velocityVariation = 0.0f; + } + + @Override + public void influenceParticle(Particle particle, EmitterShape emitterShape) { + emitterShape.getRandomPointAndNormal(particle.position, particle.velocity); + // influencing the particle's velocity + if (surfaceTangentFactor == 0.0f) { + particle.velocity.multLocal(normalVelocity); + } else { + // calculating surface tangent (velocity contains the 'normal' value) + temp.set(particle.velocity.z * surfaceTangentFactor, particle.velocity.y * surfaceTangentFactor, -particle.velocity.x * surfaceTangentFactor); + if (surfaceTangentRotation != 0.0f) {// rotating the tangent + Matrix3f m = new Matrix3f(); + m.fromAngleNormalAxis(FastMath.PI * surfaceTangentRotation, particle.velocity); + temp = m.multLocal(temp); + } + // applying normal factor (this must be done first) + particle.velocity.multLocal(normalVelocity); + // adding tangent vector + particle.velocity.addLocal(temp); + } + if (velocityVariation != 0.0f) { + this.applyVelocityVariation(particle); + } + } + + /** + * This method returns the normal velocity factor. + * @return the normal velocity factor + */ + public float getNormalVelocity() { + return normalVelocity; + } + + /** + * This method sets the normal velocity factor. + * @param normalVelocity + * the normal velocity factor + */ + public void setNormalVelocity(float normalVelocity) { + this.normalVelocity = normalVelocity; + } + + /** + * This method sets the surface tangent factor. + * @param surfaceTangentFactor + * the surface tangent factor + */ + public void setSurfaceTangentFactor(float surfaceTangentFactor) { + this.surfaceTangentFactor = surfaceTangentFactor; + } + + /** + * This method returns the surface tangent factor. + * @return the surface tangent factor + */ + public float getSurfaceTangentFactor() { + return surfaceTangentFactor; + } + + /** + * This method sets the surface tangent rotation factor. + * @param surfaceTangentRotation + * the surface tangent rotation factor + */ + public void setSurfaceTangentRotation(float surfaceTangentRotation) { + this.surfaceTangentRotation = surfaceTangentRotation; + } + + /** + * This method returns the surface tangent rotation factor. + * @return the surface tangent rotation factor + */ + public float getSurfaceTangentRotation() { + return surfaceTangentRotation; + } + + @Override + protected void applyVelocityVariation(Particle particle) { + temp.set(FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation, FastMath.nextRandomFloat() * velocityVariation); + particle.velocity.addLocal(temp); + } + + @Override + public ParticleInfluencer clone() { + NewtonianParticleInfluencer result = new NewtonianParticleInfluencer(); + result.normalVelocity = normalVelocity; + result.startVelocity = startVelocity; + result.velocityVariation = velocityVariation; + result.surfaceTangentFactor = surfaceTangentFactor; + result.surfaceTangentRotation = surfaceTangentRotation; + return result; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(normalVelocity, "normalVelocity", 0.0f); + oc.write(surfaceTangentFactor, "surfaceTangentFactor", 0.0f); + oc.write(surfaceTangentRotation, "surfaceTangentRotation", 0.0f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + normalVelocity = ic.readFloat("normalVelocity", 0.0f); + surfaceTangentFactor = ic.readFloat("surfaceTangentFactor", 0.0f); + surfaceTangentRotation = ic.readFloat("surfaceTangentRotation", 0.0f); + } +} diff --git a/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java b/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java new file mode 100644 index 0000000..56c8cf9 --- /dev/null +++ b/engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java @@ -0,0 +1,61 @@ +package com.jme3.effect.influencers; + +import com.jme3.effect.Particle; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.shapes.EmitterShape; +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; + +/** + * An interface that defines the methods to affect initial velocity of the particles. + * @author Marcin Roguski (Kaelthas) + */ +public interface ParticleInfluencer extends Savable, Cloneable { + + /** + * This method influences the particle. + * @param particle + * particle to be influenced + * @param emitterShape + * the shape of it emitter + */ + void influenceParticle(Particle particle, EmitterShape emitterShape); + + /** + * This method clones the influencer instance. + * @return cloned instance + */ + public ParticleInfluencer clone(); + + /** + * @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. + */ + void setInitialVelocity(Vector3f initialVelocity); + + /** + * This method returns the initial velocity. + * @return the initial velocity + */ + Vector3f getInitialVelocity(); + + /** + * @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. + */ + void setVelocityVariation(float variation); + + /** + * This method returns the velocity variation. + * @return the velocity variation + */ + float getVelocityVariation(); +} diff --git a/engine/src/core/com/jme3/effect/package.html b/engine/src/core/com/jme3/effect/package.html new file mode 100644 index 0000000..dd16da7 --- /dev/null +++ b/engine/src/core/com/jme3/effect/package.html @@ -0,0 +1,19 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> + +<head> +<title></title> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +</head> +<body> + +The <code>com.jme3.effect</code> package allows particle emitter effects to be +used with a jME3 application. <br/> +<p> + The <code>ParticleEmitter</code> class is the primary class used to create + particle emitter effects. See the <code>jme3test.effect</code> package + for examples on how to use <code>ParticleEmitter</code>s. + +</body> +</html> + diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java new file mode 100644 index 0000000..9838dd2 --- /dev/null +++ b/engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2010 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.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; + +public class EmitterBoxShape implements EmitterShape { + + private Vector3f min, len; + + public EmitterBoxShape() { + } + + public EmitterBoxShape(Vector3f min, Vector3f max) { + if (min == null || max == null) { + throw new NullPointerException(); + } + + this.min = min; + this.len = new Vector3f(); + this.len.set(max).subtractLocal(min); + } + + @Override + public void getRandomPoint(Vector3f store) { + store.x = min.x + len.x * FastMath.nextRandomFloat(); + store.y = min.y + len.y * FastMath.nextRandomFloat(); + store.z = min.z + len.z * FastMath.nextRandomFloat(); + } + + /** + * This method fills the point with data. + * It does not fill the normal. + * @param store the variable to store the point data + * @param normal not used in this class + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + this.getRandomPoint(store); + } + + @Override + public EmitterShape deepClone() { + try { + EmitterBoxShape clone = (EmitterBoxShape) super.clone(); + clone.min = min.clone(); + clone.len = len.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + public Vector3f getMin() { + return min; + } + + public void setMin(Vector3f min) { + this.min = min; + } + + public Vector3f getLen() { + return len; + } + + public void setLen(Vector3f len) { + this.len = len; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(min, "min", null); + oc.write(len, "length", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + min = (Vector3f) ic.readSavable("min", null); + len = (Vector3f) ic.readSavable("length", null); + } +} diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java new file mode 100644 index 0000000..1c5d687 --- /dev/null +++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java @@ -0,0 +1,63 @@ +package com.jme3.effect.shapes; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import java.util.List; + +/** + * This emiter shape emits the particles from the given shape's interior constrained by its convex hull + * (a geometry that tightly wraps the mesh). So in case of multiple meshes some vertices may appear + * in a space between them. + * @author Marcin Roguski (Kaelthas) + */ +public class EmitterMeshConvexHullShape extends EmitterMeshFaceShape { + + /** + * Empty constructor. Sets nothing. + */ + public EmitterMeshConvexHullShape() { + } + + /** + * Constructor. It stores a copy of vertex list of all meshes. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public EmitterMeshConvexHullShape(List<Mesh> meshes) { + super(meshes); + } + + /** + * This method fills the point with coordinates of randomly selected point inside a convex hull + * of randomly selected mesh. + * @param store + * the variable to store with coordinates of randomly selected selected point inside a convex hull + * of randomly selected mesh + */ + @Override + public void getRandomPoint(Vector3f store) { + super.getRandomPoint(store); + // now move the point from the meshe's face towards the center of the mesh + // the center is in (0, 0, 0) in the local coordinates + store.multLocal(FastMath.nextRandomFloat()); + } + + /** + * This method fills the point with coordinates of randomly selected point inside a convex hull + * of randomly selected mesh. + * The normal param is not used. + * @param store + * the variable to store with coordinates of randomly selected selected point inside a convex hull + * of randomly selected mesh + * @param normal + * not used in this class + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + super.getRandomPointAndNormal(store, normal); + // now move the point from the meshe's face towards the center of the mesh + // the center is in (0, 0, 0) in the local coordinates + store.multLocal(FastMath.nextRandomFloat()); + } +} diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java new file mode 100644 index 0000000..023ca5b --- /dev/null +++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java @@ -0,0 +1,97 @@ +package com.jme3.effect.shapes; + +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.util.ArrayList; +import java.util.List; + +/** + * This emiter shape emits the particles from the given shape's faces. + * @author Marcin Roguski (Kaelthas) + */ +public class EmitterMeshFaceShape extends EmitterMeshVertexShape { + + /** + * Empty constructor. Sets nothing. + */ + public EmitterMeshFaceShape() { + } + + /** + * Constructor. It stores a copy of vertex list of all meshes. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public EmitterMeshFaceShape(List<Mesh> meshes) { + super(meshes); + } + + @Override + public void setMeshes(List<Mesh> meshes) { + this.vertices = new ArrayList<List<Vector3f>>(meshes.size()); + this.normals = new ArrayList<List<Vector3f>>(meshes.size()); + for (Mesh mesh : meshes) { + Vector3f[] vertexTable = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position)); + int[] indices = new int[3]; + List<Vector3f> vertices = new ArrayList<Vector3f>(mesh.getTriangleCount() * 3); + List<Vector3f> normals = new ArrayList<Vector3f>(mesh.getTriangleCount()); + for (int i = 0; i < mesh.getTriangleCount(); ++i) { + mesh.getTriangle(i, indices); + vertices.add(vertexTable[indices[0]]); + vertices.add(vertexTable[indices[1]]); + vertices.add(vertexTable[indices[2]]); + normals.add(FastMath.computeNormal(vertexTable[indices[0]], vertexTable[indices[1]], vertexTable[indices[2]])); + } + this.vertices.add(vertices); + this.normals.add(normals); + } + } + + /** + * This method fills the point with coordinates of randomly selected point on a random face. + * @param store + * the variable to store with coordinates of randomly selected selected point on a random face + */ + @Override + public void getRandomPoint(Vector3f store) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + // the index of the first vertex of a face (must be dividable by 3) + int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1) * 3; + // put the point somewhere between the first and the second vertex of a face + float moveFactor = FastMath.nextRandomFloat(); + store.set(Vector3f.ZERO); + store.addLocal(vertices.get(meshIndex).get(vertIndex)); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor); + // move the result towards the last face vertex + moveFactor = FastMath.nextRandomFloat(); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor); + } + + /** + * This method fills the point with coordinates of randomly selected point on a random face. + * The normal param is filled with selected face's normal. + * @param store + * the variable to store with coordinates of randomly selected selected point on a random face + * @param normal + * filled with selected face's normal + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + // the index of the first vertex of a face (must be dividable by 3) + int faceIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1); + int vertIndex = faceIndex * 3; + // put the point somewhere between the first and the second vertex of a face + float moveFactor = FastMath.nextRandomFloat(); + store.set(Vector3f.ZERO); + store.addLocal(vertices.get(meshIndex).get(vertIndex)); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor); + // move the result towards the last face vertex + moveFactor = FastMath.nextRandomFloat(); + store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor); + normal.set(normals.get(meshIndex).get(faceIndex)); + } +} diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java new file mode 100644 index 0000000..28ee8b4 --- /dev/null +++ b/engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java @@ -0,0 +1,158 @@ +package com.jme3.effect.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +/** + * This emiter shape emits the particles from the given shape's vertices + * @author Marcin Roguski (Kaelthas) + */ +public class EmitterMeshVertexShape implements EmitterShape { + + protected List<List<Vector3f>> vertices; + protected List<List<Vector3f>> normals; + + /** + * Empty constructor. Sets nothing. + */ + public EmitterMeshVertexShape() { + } + + /** + * Constructor. It stores a copy of vertex list of all meshes. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public EmitterMeshVertexShape(List<Mesh> meshes) { + this.setMeshes(meshes); + } + + /** + * This method sets the meshes that will form the emiter's shape. + * @param meshes + * a list of meshes that will form the emitter's shape + */ + public void setMeshes(List<Mesh> meshes) { + Map<Vector3f, Vector3f> vertToNormalMap = new HashMap<Vector3f, Vector3f>(); + + this.vertices = new ArrayList<List<Vector3f>>(meshes.size()); + this.normals = new ArrayList<List<Vector3f>>(meshes.size()); + for (Mesh mesh : meshes) { + // fetching the data + float[] vertexTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Position)); + float[] normalTable = BufferUtils.getFloatArray(mesh.getFloatBuffer(Type.Normal)); + + // unifying normals + for (int i = 0; i < vertexTable.length; i += 3) {// the tables should have the same size and be dividable by 3 + Vector3f vert = new Vector3f(vertexTable[i], vertexTable[i + 1], vertexTable[i + 2]); + Vector3f norm = vertToNormalMap.get(vert); + if (norm == null) { + norm = new Vector3f(normalTable[i], normalTable[i + 1], normalTable[i + 2]); + vertToNormalMap.put(vert, norm); + } else { + norm.addLocal(normalTable[i], normalTable[i + 1], normalTable[i + 2]); + } + } + + // adding data to vertices and normals + List<Vector3f> vertices = new ArrayList<Vector3f>(vertToNormalMap.size()); + List<Vector3f> normals = new ArrayList<Vector3f>(vertToNormalMap.size()); + for (Entry<Vector3f, Vector3f> entry : vertToNormalMap.entrySet()) { + vertices.add(entry.getKey()); + normals.add(entry.getValue().normalizeLocal()); + } + this.vertices.add(vertices); + this.normals.add(normals); + } + } + + /** + * This method fills the point with coordinates of randomly selected mesh vertex. + * @param store + * the variable to store with coordinates of randomly selected mesh vertex + */ + @Override + public void getRandomPoint(Vector3f store) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1); + store.set(vertices.get(meshIndex).get(vertIndex)); + } + + /** + * This method fills the point with coordinates of randomly selected mesh vertex. + * The normal param is filled with selected vertex's normal. + * @param store + * the variable to store with coordinates of randomly selected mesh vertex + * @param normal + * filled with selected vertex's normal + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() - 1); + store.set(vertices.get(meshIndex).get(vertIndex)); + normal.set(normals.get(meshIndex).get(vertIndex)); + } + + @Override + public EmitterShape deepClone() { + try { + EmitterMeshVertexShape clone = (EmitterMeshVertexShape) super.clone(); + if (this.vertices != null) { + clone.vertices = new ArrayList<List<Vector3f>>(vertices.size()); + for (List<Vector3f> list : vertices) { + List<Vector3f> vectorList = new ArrayList<Vector3f>(list.size()); + for (Vector3f vector : list) { + vectorList.add(vector.clone()); + } + clone.vertices.add(vectorList); + } + } + if (this.normals != null) { + clone.normals = new ArrayList<List<Vector3f>>(normals.size()); + for (List<Vector3f> list : normals) { + List<Vector3f> vectorList = new ArrayList<Vector3f>(list.size()); + for (Vector3f vector : list) { + vectorList.add(vector.clone()); + } + clone.normals.add(vectorList); + } + } + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.writeSavableArrayList((ArrayList<List<Vector3f>>) vertices, "vertices", null); + oc.writeSavableArrayList((ArrayList<List<Vector3f>>) normals, "normals", null); + } + + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + this.vertices = ic.readSavableArrayList("vertices", null); + + List<List<Vector3f>> tmpNormals = ic.readSavableArrayList("normals", null); + if (tmpNormals != null){ + this.normals = tmpNormals; + } + } +} diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java new file mode 100644 index 0000000..f8ba70e --- /dev/null +++ b/engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2009-2010 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.shapes; + +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Vector3f; +import java.io.IOException; + +public class EmitterPointShape implements EmitterShape { + + private Vector3f point; + + public EmitterPointShape() { + } + + public EmitterPointShape(Vector3f point) { + this.point = point; + } + + @Override + public EmitterShape deepClone() { + try { + EmitterPointShape clone = (EmitterPointShape) super.clone(); + clone.point = point.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void getRandomPoint(Vector3f store) { + store.set(point); + } + + /** + * This method fills the point with data. + * It does not fill the normal. + * @param store the variable to store the point data + * @param normal not used in this class + */ + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + store.set(point); + } + + public Vector3f getPoint() { + return point; + } + + public void setPoint(Vector3f point) { + this.point = point; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(point, "point", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + this.point = (Vector3f) im.getCapsule(this).readSavable("point", null); + } +} diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterShape.java new file mode 100644 index 0000000..c23c19d --- /dev/null +++ b/engine/src/core/com/jme3/effect/shapes/EmitterShape.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2010 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.shapes; + +import com.jme3.export.Savable; +import com.jme3.math.Vector3f; + +/** + * This interface declares methods used by all shapes that represent particle emitters. + * @author Kirill + */ +public interface EmitterShape extends Savable, Cloneable { + + /** + * This method fills in the initial position of the particle. + * @param store + * store variable for initial position + */ + public void getRandomPoint(Vector3f store); + + /** + * This method fills in the initial position of the particle and its normal vector. + * @param store + * store variable for initial position + * @param normal + * store variable for initial normal + */ + public void getRandomPointAndNormal(Vector3f store, Vector3f normal); + + /** + * This method creates a deep clone of the current instance of the emitter shape. + * @return deep clone of the current instance of the emitter shape + */ + public EmitterShape deepClone(); +} diff --git a/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java b/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java new file mode 100644 index 0000000..642b279 --- /dev/null +++ b/engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2009-2010 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.shapes; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import java.io.IOException; + +public class EmitterSphereShape implements EmitterShape { + + private Vector3f center; + private float radius; + + public EmitterSphereShape() { + } + + public EmitterSphereShape(Vector3f center, float radius) { + if (center == null) { + throw new NullPointerException(); + } + + if (radius <= 0) { + throw new IllegalArgumentException("Radius must be greater than 0"); + } + + this.center = center; + this.radius = radius; + } + + @Override + public EmitterShape deepClone() { + try { + EmitterSphereShape clone = (EmitterSphereShape) super.clone(); + clone.center = center.clone(); + return clone; + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + + @Override + public void getRandomPoint(Vector3f store) { + do { + store.x = (FastMath.nextRandomFloat() * 2f - 1f) * radius; + store.y = (FastMath.nextRandomFloat() * 2f - 1f) * radius; + store.z = (FastMath.nextRandomFloat() * 2f - 1f) * radius; + } while (store.distance(center) > radius); + } + + @Override + public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { + this.getRandomPoint(store); + } + + public Vector3f getCenter() { + return center; + } + + public void setCenter(Vector3f center) { + this.center = center; + } + + public float getRadius() { + return radius; + } + + public void setRadius(float radius) { + this.radius = radius; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(center, "center", null); + oc.write(radius, "radius", 0); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + center = (Vector3f) ic.readSavable("center", null); + radius = ic.readFloat("radius", 0); + } +} |