1 package com.badlogic.gdx.graphics.g3d.particles; 2 3 import com.badlogic.gdx.assets.AssetManager; 4 import com.badlogic.gdx.graphics.g3d.particles.ParallelArray.FloatChannel; 5 import com.badlogic.gdx.graphics.g3d.particles.emitters.Emitter; 6 import com.badlogic.gdx.graphics.g3d.particles.influencers.Influencer; 7 import com.badlogic.gdx.graphics.g3d.particles.renderers.ParticleControllerRenderer; 8 import com.badlogic.gdx.math.Matrix4; 9 import com.badlogic.gdx.math.Quaternion; 10 import com.badlogic.gdx.math.Vector3; 11 import com.badlogic.gdx.math.collision.BoundingBox; 12 import com.badlogic.gdx.utils.Array; 13 import com.badlogic.gdx.utils.Json; 14 import com.badlogic.gdx.utils.JsonValue; 15 import com.badlogic.gdx.utils.reflect.ClassReflection; 16 17 /** Base class of all the particle controllers. 18 * Encapsulate the generic structure of a controller and methods to update the particles simulation. 19 * @author Inferno */ 20 public class ParticleController implements Json.Serializable, ResourceData.Configurable{ 21 22 /** the default time step used to update the simulation */ 23 protected static final float DEFAULT_TIME_STEP = 1f/60; 24 25 /** Name of the controller */ 26 public String name; 27 28 /** Controls the emission of the particles */ 29 public Emitter emitter; 30 31 /** Update the properties of the particles */ 32 public Array<Influencer> influencers; 33 34 /** Controls the graphical representation of the particles */ 35 public ParticleControllerRenderer<?, ?> renderer; 36 37 /** Particles components */ 38 public ParallelArray particles; 39 public ParticleChannels particleChannels; 40 41 /** Current transform of the controller 42 * DO NOT CHANGE MANUALLY */ 43 public Matrix4 transform; 44 45 /** Transform flags */ 46 public Vector3 scale; 47 48 /** Not used by the simulation, it should represent the bounding box containing all the particles*/ 49 protected BoundingBox boundingBox; 50 51 /** Time step, DO NOT CHANGE MANUALLY */ 52 public float deltaTime, deltaTimeSqr; 53 ParticleController()54 public ParticleController(){ 55 transform = new Matrix4(); 56 scale = new Vector3(1,1,1); 57 influencers = new Array<Influencer>(true, 3, Influencer.class); 58 setTimeStep(DEFAULT_TIME_STEP); 59 } 60 ParticleController(String name, Emitter emitter, ParticleControllerRenderer<?, ?> renderer, Influencer...influencers)61 public ParticleController(String name, Emitter emitter, ParticleControllerRenderer<?, ?> renderer, Influencer...influencers){ 62 this(); 63 this.name = name; 64 this.emitter = emitter; 65 this.renderer = renderer; 66 this.particleChannels = new ParticleChannels(); 67 this.influencers = new Array<Influencer>(influencers); 68 } 69 70 /**Sets the delta used to step the simulation */ setTimeStep(float timeStep)71 private void setTimeStep (float timeStep) { 72 deltaTime = timeStep; 73 deltaTimeSqr = deltaTime*deltaTime; 74 } 75 76 /** Sets the current transformation to the given one. 77 * @param transform the new transform matrix */ setTransform(Matrix4 transform)78 public void setTransform (Matrix4 transform) { 79 this.transform.set(transform); 80 transform.getScale(scale); 81 } 82 83 /** Sets the current transformation. */ setTransform(float x, float y, float z, float qx, float qy, float qz, float qw, float scale )84 public void setTransform(float x, float y, float z, float qx, float qy, float qz, float qw, float scale ){ 85 transform.set(x, y, z, qx, qy, qz, qw, scale, scale, scale); 86 this.scale.set(scale, scale, scale); 87 } 88 89 /** Post-multiplies the current transformation with a rotation matrix represented by the given quaternion.*/ rotate(Quaternion rotation)90 public void rotate(Quaternion rotation){ 91 this.transform.rotate(rotation); 92 } 93 94 /** Post-multiplies the current transformation with a rotation matrix by the given angle around the given axis. 95 * @param axis the rotation axis 96 * @param angle the rotation angle in degrees*/ rotate(Vector3 axis, float angle)97 public void rotate(Vector3 axis, float angle){ 98 this.transform.rotate(axis, angle); 99 } 100 101 /** Postmultiplies the current transformation with a translation matrix represented by the given translation.*/ translate(Vector3 translation)102 public void translate(Vector3 translation){ 103 this.transform.translate(translation); 104 } 105 setTranslation(Vector3 translation)106 public void setTranslation (Vector3 translation) { 107 this.transform.setTranslation(translation); 108 } 109 110 /** Postmultiplies the current transformation with a scale matrix represented by the given scale on x,y,z.*/ scale(float scaleX, float scaleY, float scaleZ)111 public void scale(float scaleX, float scaleY, float scaleZ){ 112 this.transform.scale(scaleX, scaleY, scaleZ); 113 this.transform.getScale(scale); 114 } 115 116 /** Postmultiplies the current transformation with a scale matrix represented by the given scale vector.*/ scale(Vector3 scale)117 public void scale(Vector3 scale){ 118 scale(scale.x, scale.y, scale.z); 119 } 120 121 /** Postmultiplies the current transformation with the given matrix.*/ mul(Matrix4 transform)122 public void mul(Matrix4 transform){ 123 this.transform.mul(transform); 124 this.transform.getScale(scale); 125 } 126 127 /** Set the given matrix to the current transformation matrix.*/ getTransform(Matrix4 transform)128 public void getTransform(Matrix4 transform){ 129 transform.set(this.transform); 130 } 131 isComplete()132 public boolean isComplete() { 133 return emitter.isComplete(); 134 } 135 136 /** Initialize the controller. 137 * All the sub systems will be initialized and binded to the controller. 138 * Must be called before any other method. */ init()139 public void init(){ 140 bind(); 141 if(particles != null) { 142 end(); 143 particleChannels.resetIds(); 144 } 145 allocateChannels(emitter.maxParticleCount); 146 147 emitter.init(); 148 for(Influencer influencer : influencers) 149 influencer.init(); 150 renderer.init(); 151 } 152 allocateChannels(int maxParticleCount)153 protected void allocateChannels (int maxParticleCount){ 154 particles = new ParallelArray(maxParticleCount); 155 //Alloc additional channels 156 emitter.allocateChannels(); 157 for(Influencer influencer : influencers) 158 influencer.allocateChannels(); 159 renderer.allocateChannels(); 160 } 161 162 /** Bind the sub systems to the controller 163 * Called once during the init phase.*/ bind()164 protected void bind(){ 165 emitter.set(this); 166 for(Influencer influencer : influencers) 167 influencer.set(this); 168 renderer.set(this); 169 } 170 171 /** Start the simulation. */ start()172 public void start () { 173 emitter.start(); 174 for(Influencer influencer : influencers) 175 influencer.start(); 176 } 177 178 /** Reset the simulation. */ reset()179 public void reset(){ 180 end(); 181 start(); 182 } 183 184 /** End the simulation. */ end()185 public void end () { 186 for(Influencer influencer : influencers) 187 influencer.end(); 188 emitter.end(); 189 } 190 191 /** Generally called by the Emitter. 192 * This method will notify all the sub systems that a given amount 193 * of particles has been activated. */ activateParticles(int startIndex, int count)194 public void activateParticles (int startIndex, int count) { 195 emitter.activateParticles(startIndex, count); 196 for(Influencer influencer : influencers) 197 influencer.activateParticles(startIndex, count); 198 } 199 200 /** Generally called by the Emitter. 201 * This method will notify all the sub systems that a given amount 202 * of particles has been killed. */ killParticles(int startIndex, int count)203 public void killParticles (int startIndex, int count){ 204 emitter.killParticles(startIndex, count); 205 for(Influencer influencer : influencers) 206 influencer.killParticles(startIndex, count); 207 } 208 209 /** Updates the particles data */ update()210 public void update(){ 211 emitter.update(); 212 for(Influencer influencer : influencers) 213 influencer.update(); 214 } 215 216 /**Updates the renderer used by this controller, usually this means the particles will be draw inside a batch. */ draw()217 public void draw () { 218 if(particles.size > 0){ 219 renderer.update(); 220 } 221 } 222 223 /** @return a copy of this controller*/ copy()224 public ParticleController copy () { 225 Emitter emitter = (Emitter)this.emitter.copy(); 226 Influencer[] influencers = new Influencer[this.influencers.size]; 227 int i=0; 228 for(Influencer influencer : this.influencers){ 229 influencers[i++] = (Influencer)influencer.copy(); 230 } 231 return new ParticleController(new String(this.name), emitter, (ParticleControllerRenderer<?, ?>)renderer.copy(), influencers); 232 } 233 dispose()234 public void dispose(){ 235 emitter.dispose(); 236 for(Influencer influencer : influencers) 237 influencer.dispose(); 238 } 239 240 /** @return a copy of this controller, should be used after the particle effect has been loaded. */ getBoundingBox()241 public BoundingBox getBoundingBox (){ 242 if(boundingBox == null) boundingBox = new BoundingBox(); 243 calculateBoundingBox(); 244 return boundingBox; 245 } 246 247 /** Updates the bounding box using the position channel. */ calculateBoundingBox()248 protected void calculateBoundingBox () { 249 boundingBox.clr(); 250 FloatChannel positionChannel = particles.getChannel(ParticleChannels.Position); 251 for(int pos = 0, c = positionChannel.strideSize*particles.size ; pos < c; pos += positionChannel.strideSize){ 252 boundingBox.ext( positionChannel.data[pos + ParticleChannels.XOffset], 253 positionChannel.data[pos + ParticleChannels.YOffset], 254 positionChannel.data[pos + ParticleChannels.ZOffset]); 255 } 256 } 257 258 /** @return the index of the Influencer of the given type. */ findIndex(Class<K> type)259 private <K extends Influencer> int findIndex(Class<K> type){ 260 for(int i = 0; i< influencers.size; ++i){ 261 Influencer influencer = influencers.get(i); 262 if(ClassReflection.isAssignableFrom(type, influencer.getClass())){ 263 return i; 264 } 265 } 266 return -1; 267 } 268 269 /** @return the influencer having the given type. */ findInfluencer(Class<K> influencerClass)270 public <K extends Influencer> K findInfluencer (Class<K> influencerClass) { 271 int index = findIndex(influencerClass); 272 return index >-1 ? (K)influencers.get(index) : null; 273 } 274 275 /** Removes the Influencer of the given type. */ removeInfluencer(Class<K> type)276 public <K extends Influencer> void removeInfluencer (Class<K> type) { 277 int index = findIndex(type); 278 if(index > -1 ) 279 influencers.removeIndex(index); 280 } 281 282 /** Replaces the Influencer of the given type with the one passed as parameter. */ replaceInfluencer(Class<K> type, K newInfluencer)283 public <K extends Influencer> boolean replaceInfluencer (Class<K> type, K newInfluencer) { 284 int index = findIndex(type); 285 if(index > -1){ 286 influencers.insert(index, newInfluencer); 287 influencers.removeIndex(index+1); 288 return true; 289 } 290 return false; 291 } 292 293 @Override write(Json json)294 public void write (Json json) { 295 json.writeValue("name", name); 296 json.writeValue("emitter", emitter, Emitter.class); 297 json.writeValue("influencers", influencers, Array.class, Influencer.class); 298 json.writeValue("renderer", renderer, ParticleControllerRenderer.class); 299 } 300 301 @Override read(Json json, JsonValue jsonMap)302 public void read (Json json, JsonValue jsonMap) { 303 name = json.readValue("name", String.class, jsonMap); 304 emitter = json.readValue("emitter", Emitter.class, jsonMap); 305 influencers.addAll(json.readValue("influencers", Array.class, Influencer.class, jsonMap)); 306 renderer = json.readValue("renderer", ParticleControllerRenderer.class, jsonMap); 307 } 308 309 @Override save(AssetManager manager, ResourceData data)310 public void save (AssetManager manager, ResourceData data) { 311 emitter.save(manager, data); 312 for(Influencer influencer : influencers) 313 influencer.save(manager, data); 314 renderer.save(manager, data); 315 } 316 317 @Override load(AssetManager manager, ResourceData data)318 public void load (AssetManager manager, ResourceData data) { 319 emitter.load(manager, data); 320 for(Influencer influencer : influencers) 321 influencer.load(manager, data); 322 renderer.load(manager, data); 323 } 324 } 325