/*
|
* 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;
|
|
|
/**
|
* <code>Particle</code> 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;
|
}
|
}
|
}
|
|
/**
|
* <p>
|
* 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.
|
* </p>
|
* <p>
|
* Note that this only changes the parameters of the Particle, not the
|
* geometry the particle is associated with.
|
* </p>
|
*
|
* @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();
|
}
|
}
|