/*
* Copyright (c) 2003-2009 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//package com.jmex.effects.particles;
import java.io.IOException;
import java.nio.FloatBuffer;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
//import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.util.export.InputCapsule;
import com.jme.util.export.JMEExporter;
import com.jme.util.export.JMEImporter;
import com.jme.util.export.OutputCapsule;
import com.jme.util.export.Savable;
import com.jme.util.geom.BufferUtils;
import com.jmex.effects.particles.ParticleSystem.ParticleType;
/**
* Particle
defines a single Particle of a Particle system.
* Generally, you would not interact with this class directly.
*
* @author Joshua Slack
* @version $Id: Particle.java 4133 2009-03-19 20:40:11Z blaine.dev $
*/
public class Particle implements java.io.Serializable // Savable
{
static final long serialVersionUID = 0;
public enum Status0
{
/** Particle is dead -- not in play. */
Dead, // no longer used (kept only for loading old scenes)
/** Particle is currently active. */
Alive,
// /** Particle is available for spawning. */
// Available,
/** Particle is available for spawning. */
Hidden;
}
static final int VAL_CURRENT_SIZE = 0;
static final int VAL_CURRENT_SPIN = 1;
static final int VAL_CURRENT_MASS = 2;
static final int VAL_CURRENT_LIVE = 3;
private float[] values = new float[4];
private int startIndex;
private Vector3f position = new Vector3f();
private ColorRGBA currColor = ColorRGBA.black.clone();
// private com.jmex.effects.particles.Particle.Status status = com.jmex.effects.particles.Particle.Status.Available;
private Status0 status = Status0.Dead; // Available;
private float lifeSpan;
private float size;
private float currentAge;
private int currentTexIndex = -1;
private ParticleSystem parent;
private Vector3f velocity = new Vector3f();
private Vector3f bbX = new Vector3f(), bbY = new Vector3f();
// colors
private ParticleType type = com.jmex.effects.particles.ParticleSystem.ParticleType.Point; // Quad;
private Triangle triModel;
// static variable for use in calculations to eliminate object creation
private static Vector3f tempVec3 = new Vector3f();
private static Quaternion tempQuat = new Quaternion();
int uid;
/**
* Empty constructor - mostly for use with Savable interface
*/
// public Particle()
// {
// }
/**
* Normal use constructor. Sets up the parent and particle type for this
* particle.
*
* @param parent
* the particle collection this particle belongs to
*/
public Particle(ParticleSystem parent)
{
this.parent = parent;
this.type = parent.getParticleType();
}
/**
* Cause this particle to reset it's lifespan, velocity, color, age and size
* per the parent's settings. status is set to Status.Available and location
* is set to 0,0,0. Actual geometry data is not affected by this call, only
* particle params.
*/
public void init()
{
init(parent.getRandomVelocity(null), new Vector3f(), parent.getRandomLifeSpan());
}
/**
* Cause this particle to reset it's color, age and size per the parent's
* settings. status is set to Status.Available. Location, velocity and
* lifespan are set as given. Actual geometry data is not affected by this
* call, only particle params.
*
* @param velocity
* new initial particle velocity
* @param position
* new initial particle position
* @param lifeSpan
* new particle lifespan in ms
*/
public void init(Vector3f velocity, Vector3f position, float lifeSpan)
{
this.lifeSpan = lifeSpan;
// this.velocity = (Vector3f) velocity.clone();
// this.position = (Vector3f) position.clone();
this.velocity.set(velocity);
this.position.set(position);
currColor.set(parent.getStartColor());
currentAge = 0;
status = Particle.Status0.Dead; // Available;
values[VAL_CURRENT_SIZE] = parent.getStartSize();
}
/**
* Reset particle conditions. Besides the passed lifespan, we also reset
* color, size, and spin angle to their starting values (as given by
* parent.) Status is set to Status.Available.
*
* @param lifeSpan
* the recreated particle's new lifespan
*/
public void recreateParticle(float lifeSpan, float size)
{
uid = (int)(FastMath.nextRandomFloat() * 100000);
this.lifeSpan = lifeSpan;
int verts = ParticleSystem.getVertsForParticleType(type);
currColor.set(parent.getStartColor());
for (int x = 0; x < verts; x++)
{
//BufferUtils.setInBuffer(currColor, parent.getParticleGeometry().getColorBuffer(), startIndex + x);
parent.getParticleGeometry().getColorBuffer().set(startIndex + x, currColor);
}
this.size = size;
values[VAL_CURRENT_SIZE] = parent.getStartSize();
currentAge = 0;
values[VAL_CURRENT_MASS] = 1;
status = Particle.Status0.Hidden; // Available;
values[VAL_CURRENT_LIVE] = 0;
}
/**
* Update the vertices for this particle, taking size, spin and viewer into
* consideration. In the case of particle type ParticleType.GeomMesh, the
* original triangle normal is maintained rather than rotating it to face
* the camera or parent vectors.
*
* @param cam
* Camera to use in determining viewer aspect. If null, or if
* parent is not set to camera facing, parent's left and up
* vectors are used.
*/
public void updateVerts(Camera cam)
{
float orient = parent.getParticleOrientation() + values[VAL_CURRENT_SPIN];
float currSize = values[VAL_CURRENT_SIZE];
tempVec3.x = currSize * size;
tempVec3.y = values[VAL_CURRENT_LIVE];
tempVec3.z = uid; // values[VAL_CURRENT_SPIN];
if (type == com.jmex.effects.particles.ParticleSystem.ParticleType.GeomMesh || type == com.jmex.effects.particles.ParticleSystem.ParticleType.Point)
{
; // nothing to do
} else if (cam != null && parent.isCameraFacing())
{
// if (parent.isVelocityAligned())
// {
// bbX.set(velocity).normalizeLocal().multLocal(currSize);
// cam.getDirection().cross(bbX, bbY).normalizeLocal().multLocal(
// currSize);
// } else if (orient == 0)
// {
// bbX.set(cam.getLeft()).multLocal(currSize);
// bbY.set(cam.getUp()).multLocal(currSize);
// } else
// {
// float cA = FastMath.cos(orient) * currSize;
// float sA = FastMath.sin(orient) * currSize;
// bbX.set(cam.getLeft()).multLocal(cA).addLocal(
// cam.getUp().x * sA, cam.getUp().y * sA,
// cam.getUp().z * sA);
// bbY.set(cam.getLeft()).multLocal(-sA).addLocal(
// cam.getUp().x * cA, cam.getUp().y * cA,
// cam.getUp().z * cA);
// }
} else
{
bbX.set(parent.getLeftVector()).multLocal(currSize);
bbY.set(parent.getUpVector()).multLocal(currSize);
}
switch (type)
{
case Quad:
{
// position.add(bbX, tempVec3).subtractLocal(bbY);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex);
//
// position.add(bbX, tempVec3).addLocal(bbY);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex + 1);
//
// position.subtract(bbX, tempVec3).subtractLocal(bbY);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex + 2);
//
// position.subtract(bbX, tempVec3).addLocal(bbY);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex + 3);
break;
}
case GeomMesh:
{
// Vector3f norm = triModel.getNormal();
// if (orient != 0)
// {
// tempQuat.fromAngleNormalAxis(orient, norm);
// }
//
// for (int x = 0; x < 3; x++)
// {
// if (orient != 0)
// {
// tempQuat.mult(triModel.get(x), tempVec3);
// } else
// {
// tempVec3.set(triModel.get(x));
// }
// tempVec3.multLocal(currSize).addLocal(position);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(),
// startIndex + x);
// }
break;
}
case Triangle:
{
// position.add(bbX, tempVec3).subtractLocal(bbY);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex);
//
// position.add(bbX, tempVec3).addLocal(3 * bbY.x, 3 * bbY.y,
// 3 * bbY.z);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex + 1);
//
// position.subtract(bbX.multLocal(3), tempVec3).subtractLocal(bbY);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex + 2);
break;
}
case Line:
{
// position.subtract(bbX, tempVec3);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex);
//
// position.add(bbX, tempVec3);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex + 1);
break;
}
case Point:
{
// BufferUtils.setInBuffer(position, parent.getParticleGeometry().getVertexBuffer(), startIndex);
// BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getSizeBuffer(), startIndex);
parent.getParticleGeometry().getVertexBuffer().set(startIndex, position);
parent.getParticleGeometry().getSizeBuffer().set(startIndex, tempVec3);
break;
}
}
}
/**
*
* update position (using current position and velocity), color * (interpolating between start and end color), size (interpolating between * start and end size), spin (using parent's spin speed) and current age of * particle. If this particle's age is greater than its lifespan, it is set * to status DEAD. *
** Note that this only changes the parameters of the Particle, not the * geometry the particle is associated with. *
* * @param secondsPassed * number of seconds passed since last update. * @return true if this particle is not ALIVE (in other words, if it is * ready to be reused.) */ public boolean updateAndCheck(float secondsPassed) { if (status != Particle.Status0.Alive && status != Particle.Status0.Hidden) { return true; } currentAge += secondsPassed; // * 1000; // add ms time to age if (currentAge > lifeSpan) { killParticle(); values[VAL_CURRENT_LIVE] = 0; return true; } // if (status == Particle.Status0.Hidden) // return false; position.scaleAdd(secondsPassed // * 1000f , velocity, position); // get interpolated values from appearance ramp: parent.getRamp().getValuesAtAge(currentAge, lifeSpan, currColor, values, parent); values[VAL_CURRENT_LIVE] = status == Status0.Alive ? 1 : 0; // interpolate colors int verts = ParticleSystem.getVertsForParticleType(type); for (int x = 0; x < verts; x++) { //BufferUtils.setInBuffer(currColor, parent.getParticleGeometry().getColorBuffer(), startIndex + x); parent.getParticleGeometry().getColorBuffer().set(startIndex + x, currColor); } // check for tex animation int newTexIndex = 0; // parent.getTexAnimation().getTexIndexAtAge(currentAge, lifeSpan, parent); // Update tex coords if applicable if (currentTexIndex != newTexIndex) { // Only supported in Quad type for now. if (ParticleType.Quad.equals(parent.getParticleType())) { // determine side float side = FastMath.sqrt(parent.getTexQuantity()); int index = newTexIndex; if (index >= parent.getTexQuantity()) { index %= parent.getTexQuantity(); } // figure row / col float row = side - (int) (index / side) - 1; float col = index % side; // set texcoords float sU = col / side, eU = (col + 1) / side; float sV = row / side, eV = (row + 1) / side; FloatBuffer texs = parent.getParticleGeometry().getTextureCoords(0).coords; texs.position(startIndex * 2); texs.put(sU).put(sV); texs.put(sU).put(eV); texs.put(eU).put(sV); texs.put(eU).put(eV); texs.clear(); } } return false; } public void killParticle() { setStatus(Particle.Status0.Dead); currColor.a = 0; //BufferUtils.populateFromBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex); parent.getParticleGeometry().getVertexBuffer().get(startIndex, tempVec3); int verts = ParticleSystem.getVertsForParticleType(type); for (int x = 0; x < verts; x++) { // BufferUtils.setInBuffer(tempVec3, parent.getParticleGeometry().getVertexBuffer(), startIndex + x); parent.getParticleGeometry().getVertexBuffer().set(startIndex+x, tempVec3); // BufferUtils.setInBuffer(currColor, parent.getParticleGeometry().getColorBuffer(), startIndex + x); parent.getParticleGeometry().getColorBuffer().set(startIndex+x, currColor); } } /** * Resets current age to 0 */ public void resetAge() { currentAge = 0; } /** * @return the current age of the particle in ms */ public float getCurrentAge() { return currentAge; } /** * @return the current position of the particle in space */ public Vector3f getPosition() { return position; } /** * Set the position of the particle in space. * * @param position * the new position in world coordinates */ public void setPosition(Vector3f position) { this.position.set(position); } /** * @return the current status of this particle. * @see Status */ //public com.jmex.effects.particles.Particle.Status getStatus() public Particle.Status0 getStatus() { return status; } /** * Set the status of this particle. * * @param status * new status of this particle * @see Status */ public void setStatus(Particle.Status0 status) { this.status = status; } /** * @return the current velocity of this particle */ public Vector3f getVelocity() { return velocity; } /** * Set the current velocity of this particle * * @param velocity * the new velocity */ public void setVelocity(Vector3f velocity) { this.velocity.set(velocity); } /** * @return the current color applied to this particle */ public ColorRGBA getCurrentColor() { return currColor; } /** * @return the start index of this particle in relation to where it exists * in its parent's geometry data. */ public int getStartIndex() { return startIndex; } /** * Set the starting index where this particle is represented in its parent's * geometry data * * @param index */ public void setStartIndex(int index) { this.startIndex = index; } /** * @return the mass of this particle. Only used by ParticleInfluences such * as drag. */ public float getMass() { return values[VAL_CURRENT_MASS]; } /** * @return the inverse mass of this particle. Often useful for skipping * constant division by mass calculations. If the mass is 0, the * inverse mass is considered to be positive infinity. Conversely, * if the mass is positive infinity, the inverse is 0. The inverse * of negative infinity is considered to be -0. */ public float getInvMass() { float mass = values[VAL_CURRENT_MASS]; if (mass == 0) { return Float.POSITIVE_INFINITY; } else if (mass == Float.POSITIVE_INFINITY) { return 0; } else if (mass == Float.NEGATIVE_INFINITY) { return -0; } else { return 1f / mass; } } /** * Sets a triangle model to use for particle calculations when using * particle type ParticleType.GeomMesh. The particle will maintain the * triangle's ratio and plane of orientation. It will spin (if applicable) * around the triangle's normal axis. The triangle should already have its * center and normal fields calculated before calling this method. * * @param t * the triangle to model this particle after. */ public void setTriangleModel(Triangle t) { this.triModel = t; } /** * @return the triangle model used by this particle * @see #setTriangleModel(Triangle) */ public Triangle getTriangleModel() { return this.triModel; } // ///// // Savable interface methods // ///// // public void write(JMEExporter e) throws IOException // { // OutputCapsule capsule = e.getCapsule(this); // capsule.write(startIndex, "startIndex", 0); // capsule.write(position, "position", Vector3f.ZERO); // //-- capsule.write(status, "status", com.jmex.effects.particles.Particle.Status.Available); // capsule.write(lifeSpan, "lifeSpan", 0); // capsule.write(currentAge, "currentAge", 0); // // capsule.write(parent, "parent", null); // capsule.write(velocity, "velocity", Vector3f.UNIT_XYZ); // //-- capsule.write(type, "type", ParticleSystem.ParticleType.Quad); // } // // public void read(JMEImporter e) throws IOException // { // InputCapsule capsule = e.getCapsule(this); // startIndex = capsule.readInt("startIndex", 0); // position = (Vector3f) capsule.readSavable("position", Vector3f.ZERO.clone()); // //-- status = capsule.readEnum("status", com.jmex.effects.particles.Particle.Status.class, com.jmex.effects.particles.Particle.Status.Available); // lifeSpan = capsule.readFloat("lifeSpan", 0); // currentAge = capsule.readInt("currentAge", 0); // parent = (ParticleSystem) capsule.readSavable("parent", null); // velocity = (Vector3f) capsule.readSavable("velocity", Vector3f.UNIT_XYZ.clone()); // //-- type = capsule.readEnum("type", com.jmex.effects.particles.ParticleSystem.ParticleType.class, // //-- com.jmex.effects.particles.ParticleSystem.ParticleType.Quad); // } public Class extends Particle> getClassTag() { return this.getClass(); } }