Explorar el Código

Changes to particles system:
1. Improvement in cooperation with mesh-shaped emitters.
2. Added particle influencers that calculate initial velocity.

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7564 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

Kae..pl hace 14 años
padre
commit
49116be02d

+ 22 - 6
engine/src/core/com/jme3/effect/EmitterBoxShape.java

@@ -48,21 +48,35 @@ public class EmitterBoxShape implements EmitterShape {
     }
 
     public EmitterBoxShape(Vector3f min, Vector3f max) {
-        if (min == null || max == null)
-            throw new NullPointerException();
+        if (min == null || max == null) {
+			throw new NullPointerException();
+		}
 
         this.min = min;
         this.len = new Vector3f();
         this.len.set(max).subtractLocal(min);
     }
     
-    public void getRandomPoint(Vector3f store) {
+    @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);
+    }
 
-    public EmitterShape deepClone(){
+    @Override
+	public EmitterShape deepClone(){
         try {
             EmitterBoxShape clone = (EmitterBoxShape) super.clone();
             clone.min = min.clone();
@@ -89,12 +103,14 @@ public class EmitterBoxShape implements EmitterShape {
         this.len = len;
     }
 
-    public void write(JmeExporter ex) throws IOException {
+    @Override
+	public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(min, "min", null);
         oc.write(len, "length", null);
     }
-    public void read(JmeImporter im) throws IOException {
+    @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);

+ 30 - 4
engine/src/core/com/jme3/effect/EmitterMeshConvexHullShape.java

@@ -17,20 +17,46 @@ 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
+	 * @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
+		// 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());
 	}
 }

+ 67 - 14
engine/src/core/com/jme3/effect/EmitterMeshFaceShape.java

@@ -1,10 +1,13 @@
 package com.jme3.effect;
 
+import java.util.ArrayList;
 import java.util.List;
 
 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;
 
 /**
  * This emiter shape emits the particles from the given shape's faces.
@@ -15,29 +18,79 @@ 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
+	 * @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.length-1);
-		//the index of the first vertex of a face (must be dividable by 9)
-		int vertIndex = FastMath.nextRandomInt(0, vertices[meshIndex].length / 9 - 1) * 9;
-		//put the point somewhere between the first and the second vertex of a face
+		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(vertices[meshIndex][vertIndex] + (vertices[meshIndex][vertIndex + 3] - vertices[meshIndex][vertIndex]) * moveFactor, 
-				  vertices[meshIndex][vertIndex + 1] + (vertices[meshIndex][vertIndex + 4] - vertices[meshIndex][vertIndex + 1]) * moveFactor, 
-				  vertices[meshIndex][vertIndex + 2] + (vertices[meshIndex][vertIndex + 5] - vertices[meshIndex][vertIndex + 2]) * moveFactor);
-		//move the result towards the last face vertex
+		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[meshIndex][vertIndex + 6] - store.x) * moveFactor,
-			  	  (vertices[meshIndex][vertIndex + 7] - store.y) * moveFactor,
-				  (vertices[meshIndex][vertIndex + 8] - store.z) * moveFactor);
+		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));
 	}
 }

+ 96 - 24
engine/src/core/com/jme3/effect/EmitterMeshVertexShape.java

@@ -1,8 +1,11 @@
 package com.jme3.effect;
 
 import java.io.IOException;
-import java.nio.FloatBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
@@ -18,60 +21,129 @@ import com.jme3.util.BufferUtils;
  * @author Marcin Roguski (Kaelthas)
  */
 public class EmitterMeshVertexShape implements EmitterShape {
-	protected float[][] vertices;
-	
+	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
+	 * @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
+	 * @param meshes
+	 *        a list of meshes that will form the emitter's shape
 	 */
 	public void setMeshes(List<Mesh> meshes) {
-		this.vertices = new float[meshes.size()][];
-		int i=0;
-		for(Mesh mesh : meshes) {
-			FloatBuffer floatBuffer = mesh.getFloatBuffer(Type.Position);
-			vertices[i++] = BufferUtils.getFloatArray(floatBuffer);
+		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.length-1);
-		int vertIndex = FastMath.nextRandomInt(0, vertices[meshIndex].length / 3 - 1) * 3;
-		store.set(vertices[meshIndex][vertIndex], vertices[meshIndex][vertIndex + 1], vertices[meshIndex][vertIndex + 2]);
+		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();
-            clone.vertices = vertices==null ? null : vertices.clone();
-            return clone;
-        } catch (CloneNotSupportedException e) {
-            throw new AssertionError();
-        }
+			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.write(vertices, "vertices", null);
+		oc.writeSavableArrayList((ArrayList<List<Vector3f>>) vertices, "vertices", null);
 	}
 
 	@Override
+	@SuppressWarnings("unchecked")
 	public void read(JmeImporter im) throws IOException {
-		this.vertices = im.getCapsule(this).readFloatArray2D("vertices", null);
+		this.vertices = im.getCapsule(this).readSavableArrayList("vertices", null);
 	}
 }

+ 19 - 4
engine/src/core/com/jme3/effect/EmitterPointShape.java

@@ -49,7 +49,8 @@ public class EmitterPointShape implements EmitterShape {
         this.point = point;
     }
 
-    public EmitterShape deepClone(){
+    @Override
+	public EmitterShape deepClone(){
         try {
             EmitterPointShape clone = (EmitterPointShape) super.clone();
             clone.point = point.clone();
@@ -59,9 +60,21 @@ public class EmitterPointShape implements EmitterShape {
         }
     }
 
-    public void getRandomPoint(Vector3f store) {
+    @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;
@@ -71,12 +84,14 @@ public class EmitterPointShape implements EmitterShape {
         this.point = point;
     }
 
-    public void write(JmeExporter ex) throws IOException {
+    @Override
+	public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(point, "point", null);
     }
 
-    public void read(JmeImporter im) throws IOException {
+    @Override
+	public void read(JmeImporter im) throws IOException {
         this.point = (Vector3f) im.getCapsule(this).readSavable("point", null);
     }
 

+ 25 - 2
engine/src/core/com/jme3/effect/EmitterShape.java

@@ -35,7 +35,30 @@ package com.jme3.effect;
 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 {
-    public void getRandomPoint(Vector3f store);
-    public EmitterShape deepClone();
+	/**
+	 * 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();
 }

+ 20 - 10
engine/src/core/com/jme3/effect/EmitterSphereShape.java

@@ -49,11 +49,13 @@ public class EmitterSphereShape implements EmitterShape {
     }
 
     public EmitterSphereShape(Vector3f center, float radius) {
-        if (center == null)
-            throw new NullPointerException();
+        if (center == null) {
+			throw new NullPointerException();
+		}
 
-        if (radius <= 0)
-            throw new IllegalArgumentException("Radius must be greater than 0");
+        if (radius <= 0) {
+			throw new IllegalArgumentException("Radius must be greater than 0");
+		}
 
         this.center = center;
         this.radius = radius;
@@ -70,13 +72,19 @@ public class EmitterSphereShape implements EmitterShape {
         }
     }
 
-    public void getRandomPoint(Vector3f store) {
+    @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;
+            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;
@@ -94,13 +102,15 @@ public class EmitterSphereShape implements EmitterShape {
         this.radius = radius;
     }
     
-    public void write(JmeExporter ex) throws IOException {
+    @Override
+	public void write(JmeExporter ex) throws IOException {
         OutputCapsule oc = ex.getCapsule(this);
         oc.write(center, "center", null);
         oc.write(radius, "radius", 0);
     }
 
-    public void read(JmeImporter im) throws IOException {
+    @Override
+	public void read(JmeImporter im) throws IOException {
         InputCapsule ic = im.getCapsule(this);
         center = (Vector3f) ic.readSavable("center", null);
         radius = ic.readFloat("radius", 0);

+ 194 - 113
engine/src/core/com/jme3/effect/ParticleEmitter.java

@@ -34,6 +34,8 @@ 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.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.InputCapsule;
@@ -54,11 +56,12 @@ import com.jme3.util.TempVars;
 import java.io.IOException;
 
 public class ParticleEmitter extends Geometry implements Control {
-
     private static final EmitterShape DEFAULT_SHAPE = new EmitterPointShape(Vector3f.ZERO);
-
+    private static final ParticleInfluencer DEFAULT_INFLUENCER = new DefaultParticleInfluencer();
+    
     private EmitterShape shape = DEFAULT_SHAPE;
     private ParticleMesh particleMesh;
+    private ParticleInfluencer particleInfluencer = DEFAULT_INFLUENCER;
     private ParticleMesh.Type meshType;
     private Particle[] particles;
 
@@ -68,17 +71,15 @@ public class ParticleEmitter extends Geometry implements Control {
 //    private int next = 0;
 //    private ArrayList<Integer> unusedIndices = new ArrayList<Integer>();
 
-    private boolean randomAngle = false;
-    private boolean selectRandomImage = false;
-    private boolean facingVelocity = false;
+    private boolean randomAngle;
+    private boolean selectRandomImage;
+    private boolean facingVelocity;
     private float particlesPerSec = 20;
-    private float emitCarry = 0f;
+    private float emitCarry;
     private float lowLife  = 3f;
     private float highLife = 7f;
-    private float gravity = 0.1f;
-    private float variation = 0.2f;
-    private float rotateSpeed = 0;
-    private Vector3f startVel = new Vector3f();
+    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;
@@ -91,17 +92,18 @@ public class ParticleEmitter extends Geometry implements Control {
     private float endSize = 2f;
     private boolean worldSpace = true;
 
-    private Vector3f temp = new Vector3f();
-
+    //variable that helps with computations
+    private transient Vector3f temp = new Vector3f();
+    
     @Override
     public ParticleEmitter clone(){
         ParticleEmitter clone = (ParticleEmitter) super.clone();
         clone.shape = shape.deepClone();
         clone.setNumParticles(particles.length);
-        clone.startVel = startVel.clone();
         clone.faceNormal = faceNormal.clone();
         clone.startColor = startColor.clone();
         clone.endColor = endColor.clone();
+        clone.particleInfluencer = particleInfluencer.clone();
         clone.controls.add(clone);
         return clone;
     }
@@ -110,28 +112,28 @@ public class ParticleEmitter extends Geometry implements Control {
         super(name);
 
         // ignore world transform, unless user sets inLocalSpace
-        setIgnoreTransform(true);
+        this.setIgnoreTransform(true);
 
         // particles neither receive nor cast shadows
-        setShadowMode(ShadowMode.Off);
+        this.setShadowMode(ShadowMode.Off);
 
         // particles are usually transparent
-        setQueueBucket(Bucket.Transparent);
+        this.setQueueBucket(Bucket.Transparent);
 
         meshType = type;
 
-        setNumParticles(numParticles);
+        this.setNumParticles(numParticles);
 
         controls.add(this);
 
         switch (meshType){
             case Point:
                 particleMesh = new ParticlePointMesh();
-                setMesh(particleMesh);
+                this.setMesh(particleMesh);
                 break;
             case Triangle:
                 particleMesh = new ParticleTriMesh();
-                setMesh(particleMesh);
+                this.setMesh(particleMesh);
                 break;
             default:
                 throw new IllegalStateException("Unrecognized particle type: "+meshType);
@@ -143,7 +145,8 @@ public class ParticleEmitter extends Geometry implements Control {
         super();
     }
 
-    public Control cloneForSpatial(Spatial spatial){
+    @Override
+	public Control cloneForSpatial(Spatial spatial){
         return (Control) spatial;
     }
 
@@ -154,13 +157,25 @@ public class ParticleEmitter extends Geometry implements Control {
     public EmitterShape getShape(){
         return shape;
     }
-
+    
+    public void setParticleInfluencer(ParticleInfluencer particleInfluencer) {
+		this.particleInfluencer = particleInfluencer;
+	}
+    
+    public ParticleInfluencer getParticleInfluencer() {
+		return particleInfluencer;
+	}
+
+    public ParticleMesh.Type getMeshType() {
+		return meshType;
+	}
+    
     public boolean isInWorldSpace() {
         return worldSpace;
     }
 
     public void setInWorldSpace(boolean worldSpace) {
-        setIgnoreTransform(worldSpace);
+        this.setIgnoreTransform(worldSpace);
         this.worldSpace = worldSpace;
     }
 
@@ -195,10 +210,11 @@ public class ParticleEmitter extends Geometry implements Control {
     }
 
     public Vector3f getFaceNormal() {
-        if (Vector3f.isValidVector(faceNormal))
-            return faceNormal;
-        else
-            return null;
+        if (Vector3f.isValidVector(faceNormal)) {
+			return faceNormal;
+		} else {
+			return null;
+		}
     }
 
     /**
@@ -212,10 +228,11 @@ public class ParticleEmitter extends Geometry implements Control {
      * 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;
+        if (faceNormal == null || !Vector3f.isValidVector(faceNormal)) {
+			this.faceNormal.set(Vector3f.NAN);
+		} else {
+			this.faceNormal = faceNormal;
+		}
     }
 
     public float getRotateSpeed() {
@@ -302,17 +319,54 @@ public class ParticleEmitter extends Geometry implements Control {
         this.endSize = endSize;
     }
 
-    public float getGravity() {
-        return gravity;
-    }
-
-    /**
-     * @param gravity Set the gravity, in units/sec/sec, of particles
-     * spawned.
-     */
-    public void setGravity(float gravity) {
-        this.gravity = gravity;
-    }
+	/**
+	 * This method sets the gravity value of Y axis.
+	 * By default the Y axis is the only one to have gravity value non zero.
+	 * @param gravity
+	 *        Set the gravity of Y axis, in units/sec/sec, of particles
+	 *        spawned.
+	 */
+	@Deprecated
+	public void setGravity(float gravity) {
+		this.gravity.y = gravity;
+	}
+
+	/**
+	 * This method returns the gravity vector.
+	 * @return the gravity vector
+	 */
+	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);
+	}
+
+	/**
+	 * This method sets the gravity vector.
+	 * @param gravity
+	 *        the gravity vector
+	 */
+	public void setGravity(float[] gravity) {
+		this.setGravity(gravity[0], gravity[1], gravity[2]);
+	}
+
+	/**
+	 * This method sets the gravity vector.
+	 * @param gravity
+	 *        the gravity vector
+	 */
+	public void setGravity(float x, float y, float z) {
+		this.gravity.x = x;
+		this.gravity.y = y;
+		this.gravity.z = z;
+	}
 
     public float getHighLife() {
         return highLife;
@@ -406,8 +460,14 @@ public class ParticleEmitter extends Geometry implements Control {
         this.startSize = startSize;
     }
 
+    /**
+     * This method is deprecated.
+     * Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead.
+     * @return the initial velocity for particles
+     */
+    @Deprecated
     public Vector3f getInitialVelocity(){
-        return startVel;
+        return particleInfluencer.getInitialVelocity();
     }
 
     /**
@@ -417,15 +477,27 @@ public class ParticleEmitter extends Geometry implements Control {
      * 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.startVel.set(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 variation;
+        return particleInfluencer.getVelocityVariation();
     }
 
     /**
@@ -434,9 +506,14 @@ public class ParticleEmitter extends Geometry implements Control {
      * 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.variation = variation;
+        this.particleInfluencer.setVelocityVariation(variation);
     }
 
 //    private int newIndex(){
@@ -472,38 +549,33 @@ public class ParticleEmitter extends Geometry implements Control {
         }
 
         Particle p = particles[idx];
-        if (selectRandomImage)
-            p.imageIndex = (FastMath.nextRandomInt(0, imagesY-1) * imagesX) + FastMath.nextRandomInt(0, imagesX-1);
+        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);
+        //shape.getRandomPoint(p.position);
+        particleInfluencer.influenceParticle(p, shape);
         if (worldSpace){
             p.position.addLocal(worldTransform.getTranslation());
         }
-        p.velocity.set(startVel);
-        if (randomAngle)
-            p.angle = FastMath.nextRandomFloat() * FastMath.TWO_PI;
-        if (rotateSpeed != 0)
-            p.rotateSpeed = rotateSpeed * (0.2f + (FastMath.nextRandomFloat() * 2f - 1f) * .8f);
-
-        // NOTE: Using temp variable here
-        temp.set(FastMath.nextRandomFloat(),FastMath.nextRandomFloat(),FastMath.nextRandomFloat());
-        temp.multLocal(2f);
-        temp.subtractLocal(1f,1f,1f);
-        temp.multLocal(startVel.length());
-        p.velocity.interpolate(temp, variation);
-
-        temp.set(p.position).addLocal(p.size, p.size, p.size);
+        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++;
+        ++lastUsed;
         firstUnUsed = idx + 1;
-
         return true;
     }
 
@@ -511,15 +583,14 @@ public class ParticleEmitter extends Geometry implements Control {
      * Instantly emits all the particles possible to be emitted. Any particles
      * which are currently inactive will be spawned immediately.
      */
-    @SuppressWarnings("empty-statement")
     public void emitAllParticles(){
         // Force world transform to update
-        getWorldTransform();
+        this.getWorldTransform();
 
         TempVars vars = TempVars.get();
         assert vars.lock();
         
-        BoundingBox bbox = (BoundingBox) getMesh().getBound();
+        BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
 
         Vector3f min = vars.vect1;
         Vector3f max = vars.vect2;
@@ -534,10 +605,12 @@ public class ParticleEmitter extends Geometry implements Control {
             max.set(Vector3f.NEGATIVE_INFINITY);
         }
         
-        while (emitParticle(min, max));
+        while (this.emitParticle(min, max)) {
+			;
+		}
 
         bbox.setMinMax(min, max);
-        setBoundRefresh();
+        this.setBoundRefresh();
 
         assert vars.unlock();
     }
@@ -547,9 +620,10 @@ public class ParticleEmitter extends Geometry implements Control {
      * particles will be dead and no longer visible.
      */
     public void killAllParticles(){
-        for (int i = 0; i < particles.length; i++){
-            if (particles[i].life > 0)
-                freeParticle(i);
+        for (int i = 0; i < particles.length; ++i){
+            if (particles[i].life > 0) {
+				this.freeParticle(i);
+			}
         }
     }
 
@@ -582,7 +656,7 @@ public class ParticleEmitter extends Geometry implements Control {
 
     private void updateParticleState(float tpf){
         // Force world transform to update
-        getWorldTransform();
+        this.getWorldTransform();
 
         TempVars vars = TempVars.get();
         assert vars.lock();
@@ -590,7 +664,7 @@ public class ParticleEmitter extends Geometry implements Control {
         Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY);
         Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY);
 
-        for (int i = 0; i < particles.length; i++){
+        for (int i = 0; i < particles.length; ++i){
             Particle p = particles[i];
             if (p.life == 0){ // particle is dead
 //                assert i <= firstUnUsed;
@@ -599,19 +673,21 @@ public class ParticleEmitter extends Geometry implements Control {
 
             p.life -= tpf;
             if (p.life <= 0){
-                freeParticle(i);
+                this.freeParticle(i);
                 continue;
             }
 
             // position += velocity * tpf
             p.distToCam = -1;
-            float g = gravity * tpf;
-            p.velocity.y -= g;
-
-            // NOTE: Using temp variable
+            
+            // 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);
@@ -619,15 +695,16 @@ public class ParticleEmitter extends Geometry implements Control {
 
             // 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);
+    		max.maxLocal(temp);
+    		temp.set(p.position).subtractLocal(p.size, p.size, p.size);
+    		min.minLocal(temp);
 
-            if (!selectRandomImage) // use animated effect
-                p.imageIndex = (int) (b * imagesX * imagesY);
+            if (!selectRandomImage) {
+				p.imageIndex = (int) (b * imagesX * imagesY);
+			}
             
-             if (firstUnUsed < i) {
-                swap(firstUnUsed, i);
+            if (firstUnUsed < i) {
+                this.swap(firstUnUsed, i);
                 if (i == lastUsed) {
                     lastUsed = firstUnUsed;
                 }
@@ -636,21 +713,21 @@ public class ParticleEmitter extends Geometry implements Control {
         }
 
         float particlesToEmitF = particlesPerSec * tpf;
-        int particlesToEmit = (int) (particlesToEmitF);
+        int particlesToEmit = (int) particlesToEmitF;
         emitCarry += particlesToEmitF - particlesToEmit;
 
         while (emitCarry > 1f){
-            particlesToEmit ++;
+            ++particlesToEmit;
             emitCarry -= 1f;
         }
 
-        for (int i = 0; i < particlesToEmit; i++){
-            emitParticle(min, max);
+        for (int i = 0; i < particlesToEmit; ++i){
+            this.emitParticle(min, max);
         }
 
-        BoundingBox bbox = (BoundingBox) getMesh().getBound();
+        BoundingBox bbox = (BoundingBox) this.getMesh().getBound();
         bbox.setMinMax(min, max);
-        setBoundRefresh();
+        this.setBoundRefresh();
 
         assert vars.unlock();
     }
@@ -658,29 +735,33 @@ public class ParticleEmitter extends Geometry implements Control {
     /**
      * Do not use.
      */
-    public void setSpatial(Spatial spatial) {
+    @Override
+	public void setSpatial(Spatial spatial) {
     }
 
     /**
      * @param enabled Set to enable or disable a particle. When a particle is
      * disabled, it will be "frozen in time" and not update.
      */
-    public void setEnabled(boolean enabled) {
+    @Override
+	public void setEnabled(boolean enabled) {
         this.enabled = enabled;
     }
 
-    public boolean isEnabled() {
+    @Override
+	public boolean isEnabled() {
         return enabled;
     }
 
-    public void update(float tpf) {
-        if (!enabled)
-            return;
-
-        updateParticleState(tpf);
+    @Override
+	public void update(float tpf) {
+        if (enabled) {
+        	this.updateParticleState(tpf);
+		}
     }
 
-    public void render(RenderManager rm, ViewPort vp) {
+    @Override
+	public void render(RenderManager rm, ViewPort vp) {
         Camera cam = vp.getCamera();
 
         if (meshType == ParticleMesh.Type.Point){
@@ -688,14 +769,14 @@ public class ParticleEmitter extends Geometry implements Control {
             C *= cam.getWidth() * 0.5f;
 
             // send attenuation params
-            getMaterial().setFloat("Quadratic", C);
+            this.getMaterial().setFloat("Quadratic", C);
         }
 
         Matrix3f inverseRotation = Matrix3f.IDENTITY;
         if (!worldSpace){
             TempVars vars = TempVars.get();
             assert vars.lock();
-            inverseRotation = getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
+            inverseRotation = this.getWorldRotation().toRotationMatrix(vars.tempMat3).invertLocal();
         }
         particleMesh.updateParticleData(particles, cam, inverseRotation);
         if (!worldSpace){
@@ -704,7 +785,7 @@ public class ParticleEmitter extends Geometry implements Control {
     }
 
     public void preload(RenderManager rm, ViewPort vp){
-        updateParticleState(0);
+        this.updateParticleState(0);
         particleMesh.updateParticleData(particles, vp.getCamera(), Matrix3f.IDENTITY);
     }
 
@@ -719,12 +800,10 @@ public class ParticleEmitter extends Geometry implements Control {
         oc.write(particlesPerSec, "particlesPerSec", 0);
         oc.write(lowLife, "lowLife", 0);
         oc.write(highLife, "highLife", 0);
-        oc.write(gravity, "gravity", 0);
-        oc.write(variation, "variation", 0);
+        oc.write(gravity, "gravity", null);
         oc.write(imagesX, "imagesX", 1);
         oc.write(imagesY, "imagesY", 1);
 
-        oc.write(startVel, "startVel", null);
         oc.write(startColor, "startColor", null);
         oc.write(endColor, "endColor", null);
         oc.write(startSize, "startSize", 0);
@@ -734,6 +813,8 @@ public class ParticleEmitter extends Geometry implements Control {
         oc.write(selectRandomImage, "selectRandomImage", false);
         oc.write(randomAngle, "randomAngle", false);
         oc.write(rotateSpeed, "rotateSpeed", 0);
+        
+        oc.write(particleInfluencer, "influencer", DEFAULT_INFLUENCER);
     }
 
     @Override
@@ -743,18 +824,16 @@ public class ParticleEmitter extends Geometry implements Control {
         shape = (EmitterShape) ic.readSavable("shape", DEFAULT_SHAPE);
         meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle);
         int numParticles = ic.readInt("numParticles", 0);
-        setNumParticles(numParticles);
+        this.setNumParticles(numParticles);
 
         enabled = ic.readBoolean("enabled", true);
         particlesPerSec = ic.readFloat("particlesPerSec", 0);
         lowLife = ic.readFloat("lowLife", 0);
         highLife = ic.readFloat("highLife", 0);
-        gravity = ic.readFloat("gravity", 0);
-        variation = ic.readFloat("variation", 0);
+        gravity = (Vector3f) ic.readSavable("gravity", null);
         imagesX = ic.readInt("imagesX", 1);
         imagesY = ic.readInt("imagesY", 1);
 
-        startVel = (Vector3f) ic.readSavable("startVel", null);
         startColor = (ColorRGBA) ic.readSavable("startColor", null);
         endColor = (ColorRGBA) ic.readSavable("endColor", null);
         startSize = ic.readFloat("startSize", 0);
@@ -768,16 +847,18 @@ public class ParticleEmitter extends Geometry implements Control {
         switch (meshType){
             case Point:
                 particleMesh = new ParticlePointMesh();
-                setMesh(particleMesh);
+                this.setMesh(particleMesh);
                 break;
             case Triangle:
                 particleMesh = new ParticleTriMesh();
-                setMesh(particleMesh);
+                this.setMesh(particleMesh);
                 break;
             default:
                 throw new IllegalStateException("Unrecognized particle type: "+meshType);
         }
         particleMesh.initParticleData(this, particles.length);
+        
+        particleInfluencer = (ParticleInfluencer) ic.readSavable("influencer", DEFAULT_INFLUENCER);
     }
 
 }

+ 89 - 0
engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java

@@ -0,0 +1,89 @@
+package com.jme3.effect.influencers;
+
+import java.io.IOException;
+
+import com.jme3.effect.EmitterShape;
+import com.jme3.effect.Particle;
+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;
+
+/**
+ * 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) {
+		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);
+		velocityVariation = ic.readFloat("variation", 0.2f);
+	}
+
+	@Override
+	public ParticleInfluencer clone() {
+		try {
+			return (ParticleInfluencer) super.clone();
+		} catch (CloneNotSupportedException e) {
+			return null;
+		}
+	}
+
+	@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;
+	}
+}

+ 51 - 0
engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java

@@ -0,0 +1,51 @@
+package com.jme3.effect.influencers;
+
+import java.io.IOException;
+
+import com.jme3.effect.EmitterShape;
+import com.jme3.effect.Particle;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.math.Vector3f;
+
+/**
+ * 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) {
+			return new EmptyParticleInfluencer();
+		}
+	}
+}

+ 142 - 0
engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java

@@ -0,0 +1,142 @@
+package com.jme3.effect.influencers;
+
+import java.io.IOException;
+
+import com.jme3.effect.EmitterShape;
+import com.jme3.effect.Particle;
+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;
+
+/**
+ * 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);
+	}
+}

+ 60 - 0
engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java

@@ -0,0 +1,60 @@
+package com.jme3.effect.influencers;
+
+import com.jme3.effect.EmitterShape;
+import com.jme3.effect.Particle;
+import com.jme3.effect.ParticleEmitter;
+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();
+}