/*
* 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.
*/
//import com.jmex.effects.particles.*;
import com.jmex.effects.particles.ParticleInfluence;
import com.jmex.effects.particles.ParticleControllerListener;
import java.io.IOException;
import java.util.ArrayList;
import com.jme.math.FastMath;
//import com.jme.renderer.Camera;
//import com.jme.renderer.Camera.FrustumIntersect;
//import com.jme.scene.Controller;
//import com.jme.scene.Spatial;
import com.jme.system.DisplaySystem;
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.math.Vector3f;
/**
* ParticleController
controls and maintains the parameters of a
* ParticleGeometry particle system over time.
*
* @author Joshua Slack
* @version $Id: ParticleController.java 4133 2009-03-19 20:40:11Z blaine.dev $
*/
public class ParticleController extends Controller
{
private static final long serialVersionUID = 1L;
private ParticleSystem particles;
private int particlesToCreate = 0;
private float releaseVariance;
private float currentTime;
private float prevTime;
private float releaseParticles;
private float timePassed;
private float precision;
private boolean controlFlow;
private boolean updateOnlyInView;
private Camera viewCamera;
private int iterations;
private ArrayList influences;
protected ArrayList listeners;
public ParticleController()
{
}
/**
* ParticleManager constructor
*
* @param system
* Target ParticleGeometry to act upon.
*/
public ParticleController(ParticleSystem system)
{
this.particles = system;
setMinTime(0);
setMaxTime(Float.MAX_VALUE);
setRepeatType(Controller.RT_WRAP);
setTimeStep(1.0f);
releaseVariance = 0;
controlFlow = false;
updateOnlyInView = false;
precision = .01f; // 10ms
system.updateRotationMatrix();
warmUp(60);
}
//private
Vector3f gvector = new Vector3f(0,-0.3f,0);
static Vector3f gvectorTMP = new Vector3f();
float lastcreation = 0;
void Recreate(int i)
{
// no recreation if too early
{
if (currentTime - lastcreation < particles.creationdelay)
{
return;
}
lastcreation = currentTime;
}
Particle p = particles.particles[i];
p.recreateParticle(particles.getRandomLifeSpan(), particles.getRandomSize());
if (FastMath.nextRandomFloat() < particles.density)
p.setStatus(Particle.Status0.Alive);
else
p.setStatus(Particle.Status0.Dead);
particles.initParticleLocation(i);
particles.resetParticleVelocity(i);
p.updateVerts(null);
}
static cVector tempgravity = new cVector();
/**
* Update the particles managed by this manager. If any particles are "dead"
* recreate them at the origin position (which may be a point, line or
* rectangle.) See com.jme.scene.Controller.update(float)
*
* @param secondsPassed
* float
*/
public void update(float secondsPassed)
{
// If instructed, check to see if our last frustum check passed
if (isUpdateOnlyInView())
{
// Camera cam = viewCamera != null ? viewCamera : DisplaySystem.getDisplaySystem().getRenderer().getCamera();
// int state = cam.getPlaneState();
// boolean out = false; // cam.contains(particles.getWorldBound()).equals(FrustumIntersect.Outside);
// cam.setPlaneState(state);
// if (out)
// {
// return;
// }
}
// Add time and unless we have more than precision time passed
// since last real update, do nothing
currentTime += secondsPassed * getTimeStep();
// Check precision passes
timePassed = currentTime - prevTime;
// if (timePassed < precision * getSpeed())
// {
// return;
// }
// We are actually going to do a real update,
// so this is our new previous time
prevTime = currentTime;
// Update the current rotation matrix if needed.
particles.updateRotationMatrix();
// If we are in the time window where this controller is active
// (defaults to 0 to Float.MAX_VALUE for ParticleController)
if (currentTime >= getMinTime() && currentTime <= getMaxTime())
{
// If we are controlling the flow (ie the rate of particle spawning.)
if (controlFlow)
{
// Release a number of particles based on release rate,
// timePassed (already scaled for speed) and variance. This
// is added to any current value Note this is a float value,
// so we will keep adding up partial particles
releaseParticles += (particles.getReleaseRate() *
timePassed * (1.0f + releaseVariance *
(FastMath.nextRandomFloat() - 0.5f)));
// Try to create all "whole" particles we have added up
particlesToCreate = (int) releaseParticles;
// If we have any whole particles, then subtract them from
// releaseParticles
if (particlesToCreate > 0)
{
releaseParticles -= particlesToCreate;
} // Make sure particlesToCreate is not negative
else
{
particlesToCreate = 0;
}
}
particles.updateInvScale();
// If we have any influences, prepare them all
if (influences != null)
{
for (ParticleInfluence influence : influences)
{
// influence.prepare(particles);
}
}
// Track particle index
int i = 0;
// Track whether the whole set of particles is "dead" - if any
// particles are still alive, this will be set to false
boolean dead = true;
// opposite of above boolean, but tracked seperately
boolean anyAlive = false;
// i is index through all particles
while (i < particles.particles.length) // aout 2013 getNumParticles())
{
// Current particle
Particle p = particles.getParticle(i);
// If we have influences and particle is alive
if (influences != null && p.getStatus() == Particle.Status0.Alive)
{
// Apply each enabled influence to the current particle
for (int x = 0; x < influences.size(); x++)
{
ParticleInfluence inf = influences.get(x);
if (inf.isEnabled())
{
// inf.apply(timePassed, p, i);
}
}
}
// Gravity
tempgravity.x = gvector.x;
tempgravity.y = gvector.y;
tempgravity.z = gvector.z;
LA.xformDir(tempgravity, particles.GlobalTransform(), tempgravity);
gvectorTMP.x = (float)tempgravity.x;
gvectorTMP.y = (float)tempgravity.y;
gvectorTMP.z = (float)tempgravity.z;
p.getVelocity().scaleAdd(secondsPassed, gvectorTMP, p.getVelocity());
// Update and check the particle.
// If this returns true, indicating particle is ready to be
// reused, we may reuse it. Do so if we are not using
// control flow, OR we intend to create particles based on
// control flow count calculated above
boolean reuse = p.updateAndCheck(timePassed);
if (reuse && (!controlFlow || particlesToCreate > 0))
{
// // Don't recreate the particle if it is dead, and we are clamped
// if (p.getStatus() == Particle.Status0.Dead && getRepeatType() == RT_CLAMP)
// {
// ;
//
// // We plan to reuse the particle
// } else
{
// Not all particles are dead (this one will be reused)
dead = false;
// If we are doing flow control, decrement
// particlesToCreate, since we are about to create
// one
if (controlFlow)
{
particlesToCreate--;
}
if (particles.rewind)
{
// Recreate the particle
Recreate(i);
}
}
} else if (!reuse || (controlFlow && particles.getReleaseRate() > 0))
{
// The particle wasn't dead, or we expect more particles
// later, so we're not dead!
dead = false;
}
// Check for living particles so we know when to update our boundings.
if (p.getStatus() == Particle.Status0.Alive)
{
anyAlive = true;
}
// Next particle
i++;
}
// If we are dead, deactivate and tell our listeners
if (dead)
{
setActive(false);
if (listeners != null && listeners.size() > 0)
{
for (ParticleControllerListener listener : listeners)
{
// listener.onDead(particles);
}
}
}
// If we have a bound and any live particles, update it
if (particles.getParticleGeometry().getModelBound() != null && anyAlive)
{
// particles.updateModelBound();
particles.updateWorldBoundManually();
}
}
}
/**
* Get how soon after the last update the manager will send updates to the
* particles.
*
* @return The precision.
*/
public float getPrecision()
{
return precision;
}
/**
* Set how soon after the last update the manager will send updates to the
* particles. Defaults to .01f (10ms)
*
* This means that if an update is called every 2ms (e.g. running at 500
* FPS) the particles position and stats will be updated every fifth frame
* with the elapsed time (in this case, 10ms) since previous update.
*
* @param precision
* in seconds
*/
public void setPrecision(float precision)
{
this.precision = precision;
}
/**
* Get the variance possible on the release rate. 0.0f = no variance 0.5f =
* between releaseRate / 2f and 1.5f * releaseRate
*
* @return release variance as a percent.
*/
public float getReleaseVariance()
{
return releaseVariance;
}
/**
* Set the variance possible on the release rate.
*
* @param variance
* release rate +/- variance as a percent (eg. .5 = 50%)
*/
public void setReleaseVariance(float variance)
{
this.releaseVariance = variance;
}
/**
* Does this manager regulate the particle flow?
*
* @return true if this manager regulates how many particles per sec are
* emitted.
*/
public boolean isControlFlow()
{
return controlFlow;
}
/**
* Set the regulate flow property on the manager.
*
* @param regulate
* regulate particle flow.
*/
public void setControlFlow(boolean regulate)
{
this.controlFlow = regulate;
}
/**
* Does this manager use the particle's bounding volume to limit updates?
*
* @return true if this manager only updates the particles when they are in view.
*/
public boolean isUpdateOnlyInView()
{
return updateOnlyInView;
}
/**
* Set the updateOnlyInView property on the manager.
*
* @param updateOnlyInView
* use the particle's bounding volume to limit updates.
*/
public void setUpdateOnlyInView(boolean updateOnlyInView)
{
this.updateOnlyInView = updateOnlyInView;
}
/**
* @return the camera to be used in updateOnlyInView situations. If null,
* the current displaySystem's renderer camera is used.
*/
public Camera getViewCamera()
{
return viewCamera;
}
/**
* @param viewCamera
* sets the camera to be used in updateOnlyInView situations. If
* null, the current displaySystem's renderer camera is used.
*/
public void setViewCamera(Camera viewCamera)
{
this.viewCamera = viewCamera;
}
/**
* Get the Spatial that holds all of the particle information for display.
*
* @return Spatial holding particle information.
*/
// public Spatial getParticles()
// {
// return particles;
// }
/**
* Return the number this manager has warmed up
*
* @return int
*/
public int getIterations()
{
return iterations;
}
/**
* Sets the iterations for the warmup and calls warmUp with the number of
* iterations as the argument
*
* @param iterations
*/
public void setIterations(int iterations)
{
this.iterations = iterations;
}
/**
* Add an external influence to this particle controller.
*
* @param influence
* ParticleInfluence
*/
public void addInfluence(ParticleInfluence influence)
{
if (influences == null)
{
influences = new ArrayList(1);
}
influences.add(influence);
}
/**
* Remove an influence from this particle controller.
*
* @param influence
* ParticleInfluence
* @return true if found and removed.
*/
public boolean removeInfluence(ParticleInfluence influence)
{
if (influences == null)
{
return false;
}
return influences.remove(influence);
}
/**
* Returns the list of influences acting on this particle controller.
*
* @return ArrayList
*/
public ArrayList getInfluences()
{
return influences;
}
public void clearInfluences()
{
if (influences != null)
{
influences.clear();
}
}
/**
* Subscribe a listener to receive mouse events. Enable event generation.
*
* @param listener to be subscribed
*/
public void addListener(ParticleControllerListener listener)
{
if (listeners == null)
{
listeners = new ArrayList();
}
listeners.add(listener);
}
/**
* Unsubscribe a listener. Disable event generation if no more listeners.
*
* @param listener to be unsuscribed
* @see #addListener(ParticleControllerListener)
*/
public void removeListener(ParticleControllerListener listener)
{
if (listeners != null)
{
listeners.remove(listener);
}
}
/**
* Remove all listeners and disable event generation.
*/
public void removeListeners()
{
if (listeners != null)
{
listeners.clear();
}
}
/**
* Check if a listener is allready added to this ParticleController
* @param listener listener to check for
* @return true if listener is contained in the listenerlist
*/
public boolean containsListener(ParticleControllerListener listener)
{
if (listeners != null)
{
return listeners.contains(listener);
}
return false;
}
/**
* Get all added ParticleController listeners
* @return ArrayList of listeners added to this ParticleController
*/
public ArrayList getListeners()
{
return listeners;
}
/**
* Runs the update method of this particle manager for iteration seconds
* with an update every .1 seconds (IE iterations
* 10
* update(.1f) calls). This is used to "warm up" and get the particle
* manager going.
*
* @param iterations
* The number of iterations to warm up.
*/
public void warmUp(int iterations)
{
// first set the initial positions of all the verts
particles.initAllParticlesLocation();
iterations *= 0; // 10;
for (int i = iterations; --i >= 0;)
{
update(.1f);
}
}
public void write(JMEExporter e) throws IOException
{
super.write(e);
OutputCapsule capsule = e.getCapsule(this);
// capsule.write(particles, "particleMesh", null);
capsule.write(releaseVariance, "releaseVariance", 0);
capsule.write(precision, "precision", 0);
capsule.write(controlFlow, "controlFlow", false);
capsule.write(updateOnlyInView, "updateOnlyInView", false);
capsule.write(iterations, "iterations", 0);
capsule.writeSavableArrayList(influences, "influences", null);
}
@SuppressWarnings("unchecked")
public void read(JMEImporter e) throws IOException
{
super.read(e);
InputCapsule capsule = e.getCapsule(this);
particles = (ParticleSystem) capsule.readSavable("particleMesh", null);
releaseVariance = capsule.readFloat("releaseVariance", 0);
precision = capsule.readFloat("precision", 0);
controlFlow = capsule.readBoolean("controlFlow", false);
updateOnlyInView = capsule.readBoolean("updateOnlyInView", false);
iterations = capsule.readInt("iterations", 0);
influences = capsule.readSavableArrayList("influences", null);
}
}