aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/effect
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/effect')
-rw-r--r--engine/src/core/com/jme3/effect/Particle.java94
-rw-r--r--engine/src/core/com/jme3/effect/ParticleComparator.java77
-rw-r--r--engine/src/core/com/jme3/effect/ParticleEmitter.java1206
-rw-r--r--engine/src/core/com/jme3/effect/ParticleMesh.java86
-rw-r--r--engine/src/core/com/jme3/effect/ParticlePointMesh.java166
-rw-r--r--engine/src/core/com/jme3/effect/ParticleTriMesh.java284
-rw-r--r--engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java92
-rw-r--r--engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java55
-rw-r--r--engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java142
-rw-r--r--engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java61
-rw-r--r--engine/src/core/com/jme3/effect/package.html19
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterBoxShape.java118
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java63
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterMeshFaceShape.java97
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterMeshVertexShape.java158
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterPointShape.java96
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterShape.java64
-rw-r--r--engine/src/core/com/jme3/effect/shapes/EmitterSphereShape.java117
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);
+ }
+}