/******************************************************************************* * 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); } }