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 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 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 */ 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 */ 78 public void setTransform (Matrix4 transform) { 79 this.transform.set(transform); 80 transform.getScale(scale); 81 } 82 83 /** Sets the current transformation. */ 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.*/ 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*/ 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.*/ 102 public void translate(Vector3 translation){ 103 this.transform.translate(translation); 104 } 105 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.*/ 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.*/ 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.*/ 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.*/ 128 public void getTransform(Matrix4 transform){ 129 transform.set(this.transform); 130 } 131 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. */ 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 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.*/ 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. */ 172 public void start () { 173 emitter.start(); 174 for(Influencer influencer : influencers) 175 influencer.start(); 176 } 177 178 /** Reset the simulation. */ 179 public void reset(){ 180 end(); 181 start(); 182 } 183 184 /** End the simulation. */ 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. */ 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. */ 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 */ 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. */ 217 public void draw () { 218 if(particles.size > 0){ 219 renderer.update(); 220 } 221 } 222 223 /** @return a copy of this controller*/ 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 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. */ 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. */ 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. */ 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. */ 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. */ 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. */ 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 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 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 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 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