From 49116be02d82331f36b81bc5c157ead51d996fa3 Mon Sep 17 00:00:00 2001 From: "Kae..pl" Date: Fri, 10 Jun 2011 16:16:23 +0000 Subject: [PATCH] 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 --- .../core/com/jme3/effect/EmitterBoxShape.java | 28 +- .../effect/EmitterMeshConvexHullShape.java | 34 +- .../com/jme3/effect/EmitterMeshFaceShape.java | 81 ++++- .../jme3/effect/EmitterMeshVertexShape.java | 120 +++++-- .../com/jme3/effect/EmitterPointShape.java | 23 +- .../core/com/jme3/effect/EmitterShape.java | 27 +- .../com/jme3/effect/EmitterSphereShape.java | 30 +- .../core/com/jme3/effect/ParticleEmitter.java | 307 +++++++++++------- .../DefaultParticleInfluencer.java | 89 +++++ .../influencers/EmptyParticleInfluencer.java | 51 +++ .../NewtonianParticleInfluencer.java | 142 ++++++++ .../influencers/ParticleInfluencer.java | 60 ++++ 12 files changed, 815 insertions(+), 177 deletions(-) create mode 100644 engine/src/core/com/jme3/effect/influencers/DefaultParticleInfluencer.java create mode 100644 engine/src/core/com/jme3/effect/influencers/EmptyParticleInfluencer.java create mode 100644 engine/src/core/com/jme3/effect/influencers/NewtonianParticleInfluencer.java create mode 100644 engine/src/core/com/jme3/effect/influencers/ParticleInfluencer.java diff --git a/engine/src/core/com/jme3/effect/EmitterBoxShape.java b/engine/src/core/com/jme3/effect/EmitterBoxShape.java index ae80e2079..6385623a9 100644 --- a/engine/src/core/com/jme3/effect/EmitterBoxShape.java +++ b/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); diff --git a/engine/src/core/com/jme3/effect/EmitterMeshConvexHullShape.java b/engine/src/core/com/jme3/effect/EmitterMeshConvexHullShape.java index 0fe3dbc04..7a428a51c 100644 --- a/engine/src/core/com/jme3/effect/EmitterMeshConvexHullShape.java +++ b/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 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()); } } diff --git a/engine/src/core/com/jme3/effect/EmitterMeshFaceShape.java b/engine/src/core/com/jme3/effect/EmitterMeshFaceShape.java index d9c6f7094..a84322261 100644 --- a/engine/src/core/com/jme3/effect/EmitterMeshFaceShape.java +++ b/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 meshes) { super(meshes); } - + + @Override + public void setMeshes(List meshes) { + this.vertices = new ArrayList>(meshes.size()); + this.normals = new ArrayList>(meshes.size()); + for (Mesh mesh : meshes) { + Vector3f[] vertexTable = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position)); + int[] indices = new int[3]; + List vertices = new ArrayList(mesh.getTriangleCount() * 3); + List normals = new ArrayList(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)); } } diff --git a/engine/src/core/com/jme3/effect/EmitterMeshVertexShape.java b/engine/src/core/com/jme3/effect/EmitterMeshVertexShape.java index 2a46327b0..393781bcd 100644 --- a/engine/src/core/com/jme3/effect/EmitterMeshVertexShape.java +++ b/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> vertices; + protected List> 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 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 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 vertToNormalMap = new HashMap(); + + this.vertices = new ArrayList>(meshes.size()); + this.normals = new ArrayList>(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 vertices = new ArrayList(vertToNormalMap.size()); + List normals = new ArrayList(vertToNormalMap.size()); + for (Entry 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>(vertices.size()); + for (List list : vertices) { + List vectorList = new ArrayList(list.size()); + for (Vector3f vector : list) { + vectorList.add(vector.clone()); + } + clone.vertices.add(vectorList); + } + } + if (this.normals != null) { + clone.normals = new ArrayList>(normals.size()); + for (List list : normals) { + List vectorList = new ArrayList(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>) 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); } } diff --git a/engine/src/core/com/jme3/effect/EmitterPointShape.java b/engine/src/core/com/jme3/effect/EmitterPointShape.java index 942bc405e..16cb78809 100644 --- a/engine/src/core/com/jme3/effect/EmitterPointShape.java +++ b/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); } diff --git a/engine/src/core/com/jme3/effect/EmitterShape.java b/engine/src/core/com/jme3/effect/EmitterShape.java index 49d47146a..c51eac2d2 100644 --- a/engine/src/core/com/jme3/effect/EmitterShape.java +++ b/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(); } diff --git a/engine/src/core/com/jme3/effect/EmitterSphereShape.java b/engine/src/core/com/jme3/effect/EmitterSphereShape.java index 235905e69..2f857f0ab 100644 --- a/engine/src/core/com/jme3/effect/EmitterSphereShape.java +++ b/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); diff --git a/engine/src/core/com/jme3/effect/ParticleEmitter.java b/engine/src/core/com/jme3/effect/ParticleEmitter.java index bbb34f75c..122894601 100644 --- a/engine/src/core/com/jme3/effect/ParticleEmitter.java +++ b/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 unusedIndices = new ArrayList(); - 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); } } 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 000000000..a666629dc --- /dev/null +++ b/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; + } +} 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 000000000..4d9c6964f --- /dev/null +++ b/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(); + } + } +} 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 000000000..f7498b0bd --- /dev/null +++ b/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); + } +} 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 000000000..948c959d3 --- /dev/null +++ b/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. variation 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(); +}