/*******************************************************************************
* Copyright 2011 See AUTHORS file.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.badlogic.gdx.graphics.g3d.particles;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.g3d.particles.ParallelArray.FloatChannel;
import com.badlogic.gdx.graphics.g3d.particles.emitters.Emitter;
import com.badlogic.gdx.graphics.g3d.particles.influencers.Influencer;
import com.badlogic.gdx.graphics.g3d.particles.renderers.ParticleControllerRenderer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Quaternion;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.math.collision.BoundingBox;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.Json;
import com.badlogic.gdx.utils.JsonValue;
import com.badlogic.gdx.utils.reflect.ClassReflection;
/** Base class of all the particle controllers. Encapsulate the generic structure of a controller and methods to update the
* particles simulation.
* @author Inferno */
public class ParticleController implements Json.Serializable, ResourceData.Configurable {
/** the default time step used to update the simulation */
protected static final float DEFAULT_TIME_STEP = 1f / 60;
/** Name of the controller */
public String name;
/** Controls the emission of the particles */
public Emitter emitter;
/** Update the properties of the particles */
public Array<Influencer> influencers;
/** Controls the graphical representation of the particles */
public ParticleControllerRenderer<?, ?> renderer;
/** Particles components */
public ParallelArray particles;
public ParticleChannels particleChannels;
/** Current transform of the controller DO NOT CHANGE MANUALLY */
public Matrix4 transform;
/** Transform flags */
public Vector3 scale;
/** Not used by the simulation, it should represent the bounding box containing all the particles */
protected BoundingBox boundingBox;
/** Time step, DO NOT CHANGE MANUALLY */
public float deltaTime, deltaTimeSqr;
public ParticleController () {
transform = new Matrix4();
scale = new Vector3(1, 1, 1);
influencers = new Array<Influencer>(true, 3, Influencer.class);
setTimeStep(DEFAULT_TIME_STEP);
}
public ParticleController (String name, Emitter emitter, ParticleControllerRenderer<?, ?> renderer, Influencer... influencers) {
this();
this.name = name;
this.emitter = emitter;
this.renderer = renderer;
this.particleChannels = new ParticleChannels();
this.influencers = new Array<Influencer>(influencers);
}
/** Sets the delta used to step the simulation */
private void setTimeStep (float timeStep) {
deltaTime = timeStep;
deltaTimeSqr = deltaTime * deltaTime;
}
/** Sets the current transformation to the given one.
* @param transform the new transform matrix */
public void setTransform (Matrix4 transform) {
this.transform.set(transform);
transform.getScale(scale);
}
/** Sets the current transformation. */
public void setTransform (float x, float y, float z, float qx, float qy, float qz, float qw, float scale) {
transform.set(x, y, z, qx, qy, qz, qw, scale, scale, scale);
this.scale.set(scale, scale, scale);
}
/** Post-multiplies the current transformation with a rotation matrix represented by the given quaternion. */
public void rotate (Quaternion rotation) {
this.transform.rotate(rotation);
}
/** Post-multiplies the current transformation with a rotation matrix by the given angle around the given axis.
* @param axis the rotation axis
* @param angle the rotation angle in degrees */
public void rotate (Vector3 axis, float angle) {
this.transform.rotate(axis, angle);
}
/** Postmultiplies the current transformation with a translation matrix represented by the given translation. */
public void translate (Vector3 translation) {
this.transform.translate(translation);
}
public void setTranslation (Vector3 translation) {
this.transform.setTranslation(translation);
}
/** Postmultiplies the current transformation with a scale matrix represented by the given scale on x,y,z. */
public void scale (float scaleX, float scaleY, float scaleZ) {
this.transform.scale(scaleX, scaleY, scaleZ);
this.transform.getScale(scale);
}
/** Postmultiplies the current transformation with a scale matrix represented by the given scale vector. */
public void scale (Vector3 scale) {
scale(scale.x, scale.y, scale.z);
}
/** Postmultiplies the current transformation with the given matrix. */
public void mul (Matrix4 transform) {
this.transform.mul(transform);
this.transform.getScale(scale);
}
/** Set the given matrix to the current transformation matrix. */
public void getTransform (Matrix4 transform) {
transform.set(this.transform);
}
public boolean isComplete () {
return emitter.isComplete();
}
/** Initialize the controller. All the sub systems will be initialized and binded to the controller. Must be called before any
* other method. */
public void init () {
bind();
if (particles != null) {
end();
particleChannels.resetIds();
}
allocateChannels(emitter.maxParticleCount);
emitter.init();
for (Influencer influencer : influencers)
influencer.init();
renderer.init();
}
protected void allocateChannels (int maxParticleCount) {
particles = new ParallelArray(maxParticleCount);
// Alloc additional channels
emitter.allocateChannels();
for (Influencer influencer : influencers)
influencer.allocateChannels();
renderer.allocateChannels();
}
/** Bind the sub systems to the controller Called once during the init phase. */
protected void bind () {
emitter.set(this);
for (Influencer influencer : influencers)
influencer.set(this);
renderer.set(this);
}
/** Start the simulation. */
public void start () {
emitter.start();
for (Influencer influencer : influencers)
influencer.start();
}
/** Reset the simulation. */
public void reset () {
end();
start();
}
/** End the simulation. */
public void end () {
for (Influencer influencer : influencers)
influencer.end();
emitter.end();
}
/** Generally called by the Emitter. This method will notify all the sub systems that a given amount of particles has been
* activated. */
public void activateParticles (int startIndex, int count) {
emitter.activateParticles(startIndex, count);
for (Influencer influencer : influencers)
influencer.activateParticles(startIndex, count);
}
/** Generally called by the Emitter. This method will notify all the sub systems that a given amount of particles has been
* killed. */
public void killParticles (int startIndex, int count) {
emitter.killParticles(startIndex, count);
for (Influencer influencer : influencers)
influencer.killParticles(startIndex, count);
}
/** Updates the particles data */
public void update () {
emitter.update();
for (Influencer influencer : influencers)
influencer.update();
}
/** Updates the renderer used by this controller, usually this means the particles will be draw inside a batch. */
public void draw () {
if (particles.size > 0) {
renderer.update();
}
}
/** @return a copy of this controller */
public ParticleController copy () {
Emitter emitter = (Emitter)this.emitter.copy();
Influencer[] influencers = new Influencer[this.influencers.size];
int i = 0;
for (Influencer influencer : this.influencers) {
influencers[i++] = (Influencer)influencer.copy();
}
return new ParticleController(new String(this.name), emitter, (ParticleControllerRenderer<?, ?>)renderer.copy(),
influencers);
}
public void dispose () {
emitter.dispose();
for (Influencer influencer : influencers)
influencer.dispose();
}
/** @return a copy of this controller, should be used after the particle effect has been loaded. */
public BoundingBox getBoundingBox () {
if (boundingBox == null) boundingBox = new BoundingBox();
calculateBoundingBox();
return boundingBox;
}
/** Updates the bounding box using the position channel. */
protected void calculateBoundingBox () {
boundingBox.clr();
FloatChannel positionChannel = particles.getChannel(ParticleChannels.Position);
for (int pos = 0, c = positionChannel.strideSize * particles.size; pos < c; pos += positionChannel.strideSize) {
boundingBox.ext(positionChannel.data[pos + ParticleChannels.XOffset], positionChannel.data[pos
+ ParticleChannels.YOffset], positionChannel.data[pos + ParticleChannels.ZOffset]);
}
}
/** @return the index of the Influencer of the given type. */
private <K extends Influencer> int findIndex (Class<K> type) {
for (int i = 0; i < influencers.size; ++i) {
Influencer influencer = influencers.get(i);
if (ClassReflection.isAssignableFrom(type, influencer.getClass())) {
return i;
}
}
return -1;
}
/** @return the influencer having the given type. */
public <K extends Influencer> K findInfluencer (Class<K> influencerClass) {
int index = findIndex(influencerClass);
return index > -1 ? (K)influencers.get(index) : null;
}
/** Removes the Influencer of the given type. */
public <K extends Influencer> void removeInfluencer (Class<K> type) {
int index = findIndex(type);
if (index > -1) influencers.removeIndex(index);
}
/** Replaces the Influencer of the given type with the one passed as parameter. */
public <K extends Influencer> boolean replaceInfluencer (Class<K> type, K newInfluencer) {
int index = findIndex(type);
if (index > -1) {
influencers.insert(index, newInfluencer);
influencers.removeIndex(index + 1);
return true;
}
return false;
}
@Override
public void write (Json json) {
json.writeValue("name", name);
json.writeValue("emitter", emitter, Emitter.class);
json.writeValue("influencers", influencers, Array.class, Influencer.class);
json.writeValue("renderer", renderer, ParticleControllerRenderer.class);
}
@Override
public void read (Json json, JsonValue jsonMap) {
name = json.readValue("name", String.class, jsonMap);
emitter = json.readValue("emitter", Emitter.class, jsonMap);
influencers.addAll(json.readValue("influencers", Array.class, Influencer.class, jsonMap));
renderer = json.readValue("renderer", ParticleControllerRenderer.class, jsonMap);
}
@Override
public void save (AssetManager manager, ResourceData data) {
emitter.save(manager, data);
for (Influencer influencer : influencers)
influencer.save(manager, data);
renderer.save(manager, data);
}
@Override
public void load (AssetManager manager, ResourceData data) {
emitter.load(manager, data);
for (Influencer influencer : influencers)
influencer.load(manager, data);
renderer.load(manager, data);
}
}