1 /** 2 * The Render Engine 3 * ParticleEngine 4 * 5 * @fileoverview The particle engine class. 6 * 7 * @author: Brett Fattori (brettf@renderengine.com) 8 * 9 * @author: $Author: bfattori@gmail.com $ 10 * @version: $Revision: 1571 $ 11 * 12 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 13 * 14 * Permission is hereby granted, free of charge, to any person obtaining a copy 15 * of this software and associated documentation files (the "Software"), to deal 16 * in the Software without restriction, including without limitation the rights 17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 * copies of the Software, and to permit persons to whom the Software is 19 * furnished to do so, subject to the following conditions: 20 * 21 * The above copyright notice and this permission notice shall be included in 22 * all copies or substantial portions of the Software. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 * THE SOFTWARE. 31 * 32 */ 33 34 // The class this file defines and its required classes 35 R.Engine.define({ 36 "class":"R.particles.ParticleEngine", 37 "requires":[ 38 "R.engine.BaseObject", 39 "R.struct.Container" 40 ] 41 }); 42 43 /** 44 * @class The particle engine is a system for updating and expiring 45 * particles within a game environment. This is registered with the 46 * render context so it will be updated at regular intervals. The maximum 47 * number of supported particles can be configured, but defaults to 250. 48 * It is possible to run multiple particle engines within a render context. 49 * <p/> 50 * Particles should be simple objects which don't need to perform many 51 * calculations before being drawn. All particles are rendered in world 52 * coordinates to speed up processing. 53 * <p/> 54 * A word of caution: <em>Using a particle engine will potentially slow down your frame 55 * rate depending on the amount of particles per frame.</em> While care has 56 * been taken to make the particle engine run as fast as possible, it is 57 * not uncommon to see a significant drop in frame rate when using a lot of 58 * particles. 59 * <p/> 60 * You can modify the maximum number of particles the engine will allow 61 * with the <code>R.Engine.options["maxParticles"]</code> setting. Each 62 * browser has been tailored for the best performance, but this values 63 * can be changed in your game with either {@link #setMaximum} or by 64 * changing the engine option. 65 * 66 * @extends R.engine.BaseObject 67 * @constructor 68 * @description Create a particle engine 69 */ 70 R.particles.ParticleEngine = function () { 71 return R.engine.BaseObject.extend(/** @scope R.particles.ParticleEngine.prototype */{ 72 73 particles:null, 74 particleEffects: null, 75 liveParticles:0, 76 lastTime:0, 77 maximum:0, 78 force:0, 79 80 /** @private */ 81 constructor:function () { 82 this.base("ParticleEngine"); 83 this.particles = R.struct.Container.create(); 84 this.particleEffects = R.struct.Container.create(); 85 this.maximum = R.Engine.options["maxParticles"]; 86 this.liveParticles = 0; 87 }, 88 89 /** 90 * Destroy the particle engine and all contained particles 91 */ 92 destroy:function () { 93 this.reset(); 94 this.particles.destroy(); 95 this.particleEffects.destroy(); 96 this.base(); 97 }, 98 99 /** 100 * Releases the particle engine back into the pool. 101 */ 102 release:function () { 103 this.base(); 104 this.particles = null; 105 this.particleEffects = null; 106 this.lastTime = 0; 107 this.maximum = 0; 108 this.liveParticles = 0; 109 }, 110 111 /** 112 * Add a group of particles at one time. This reduces the number of calls 113 * to {@link #addParticle} which resorts the array of particles each time. 114 * @param particles {Array|R.struct.Container} A container of particles to add at one time 115 */ 116 addParticles:function (particles) { 117 if ($.isArray(particles)) { 118 // If the particles are an Array, convert to a LinkedList first 119 particles = R.struct.Container.fromArray(particles); 120 } 121 122 if (R.Engine.options.disableParticleEngine) { 123 particles.destroy(); 124 return; 125 } 126 127 // If the new particles exceed the size of the engine's 128 // maximum, truncate the remainder 129 if (particles.size() > this.maximum) { 130 var discard = particles.reduce(this.maximum); 131 discard.cleanUp(); 132 discard.destroy(); 133 } 134 135 // Initialize all of the new particles 136 for (var i = particles.iterator(); i.hasNext();) { 137 // TODO: Why this.lastTime?? 138 i.next().init(this, this.lastTime); 139 } 140 i.destroy(); 141 142 // The maximum number of particles to animate 143 var total = this.liveParticles + particles.size(); 144 if (total > this.maximum) { 145 total = this.maximum; 146 } 147 148 // If we can fit the entire set of particles without overflowing, 149 // add all the particles and be done. 150 if (particles.size() <= this.maximum - this.liveParticles) { 151 this.particles.addAll(particles); 152 } else { 153 // There isn't enough space to put all of the particles into 154 // the container. So, we'll only add what we can. 155 var maxLeft = this.maximum - total; 156 var easySet = particles.subset(0, maxLeft); 157 this.particles.addAll(easySet); 158 easySet.destroy(); 159 } 160 particles.destroy(); 161 this.liveParticles = this.particles.size(); 162 }, 163 164 /** 165 * Add a single particle to the engine. If many particles are being 166 * added at one time, use {@link #addParticles} instead to add a 167 * {@link R.struct.Container} of particles. 168 * 169 * @param particle {R.particles.AbstractParticle} A particle to animate 170 */ 171 addParticle:function (particle) { 172 if (R.Engine.options.disableParticleEngine) { 173 particle.destroy(); 174 return; 175 } 176 177 if (this.particles.size() < this.maximum) { 178 // TODO: Why this.lastTime? 179 particle.init(this, this.lastTime); 180 this.particles.add(particle); 181 this.liveParticles = this.particles.size(); 182 } else { 183 // nowhere to put it 184 particle.destroy(); 185 } 186 }, 187 188 /** 189 * Set the absolute maximum number of particles the engine will allow. The 190 * engine is configured with a maximum number in <code>R.Engine.options["maxParticles"]</code>. 191 * You can override this value using configurations also. 192 * 193 * @param maximum {Number} The maximum particles the particle engine allows 194 */ 195 setMaximum:function (maximum) { 196 var oldMax = this.maximum; 197 this.maximum = maximum; 198 199 // Kill off particles if the size is reduced 200 if (this.maximum < oldMax) { 201 var discard = this.particles.reduce(this.maximum); 202 discard.cleanUp(); 203 discard.destroy(); 204 } 205 }, 206 207 /** 208 * Get the maximum number of particles allowed in the particle engine. 209 * @return {Number} 210 */ 211 getMaximum:function () { 212 return this.maximum; 213 }, 214 215 /** 216 * Update a particle, removing it and nulling its reference 217 * if it is dead. Only live particles are updated 218 * @private 219 */ 220 runParticle:function (particle, renderContext, time, dt) { 221 if (!particle.update(renderContext, time, dt)) { 222 this.particles.remove(particle); 223 particle.destroy(); 224 } 225 }, 226 227 /** 228 * Run a particle effect 229 * @param particleEffect 230 * @return {R.particles.Effect} The instance of the effect 231 */ 232 addEffect: function(particleEffect) { 233 this.particleEffects.add(particleEffect); 234 return particleEffect; 235 }, 236 237 /** 238 * Update the particles within the render context, and for the specified time. 239 * 240 * @param renderContext {R.rendercontexts.AbstractRenderContext} The context the particles will be rendered within. 241 * @param time {Number} The global time within the engine. 242 * @param dt {Number} The delta between the world time and the last time the world was updated 243 * in milliseconds. 244 */ 245 update:function (renderContext, time, dt) { 246 if (R.Engine.options.disableParticleEngine) { 247 return; 248 } 249 250 // Run all queued effects 251 var dead = R.struct.Container.create(); 252 for (var effectItr = this.particleEffects.iterator(); effectItr.hasNext(); ) { 253 var effect = effectItr.next(); 254 if (!effect.hasRun() || effect.getLifespan(dt) > 0) { 255 effect.runEffect(this, time, dt); 256 } else { 257 // Dead effect - these are cleaned up later 258 dead.add(effect); 259 } 260 } 261 262 this.lastTime = time; 263 264 R.debug.Metrics.add("particles", this.liveParticles, false, "#"); 265 266 // If there are no live particles, don't do anything 267 if (this.liveParticles == 0) { 268 return; 269 } 270 271 renderContext.pushTransform(); 272 273 for (var itr = this.particles.iterator(); itr.hasNext();) { 274 this.runParticle(itr.next(), renderContext, time, dt); 275 } 276 itr.destroy(); 277 278 renderContext.popTransform(); 279 this.liveParticles = this.particles.size(); 280 281 // Remove dead effects 282 for (var deadItr = dead.iterator(); deadItr.hasNext(); ) { 283 var deadEffect = deadItr.next(); 284 this.particleEffects.remove(deadEffect); 285 } 286 dead.destroy(); 287 }, 288 289 /** 290 * Get the properties object for the particle engine 291 * @return {Object} 292 */ 293 getProperties:function () { 294 var self = this; 295 var prop = this.base(self); 296 return $.extend(prop, { 297 "Particles":[function () { 298 return self.particles.size(); 299 }, 300 null, false] 301 }); 302 }, 303 304 /** 305 * Reset the particle engine. Destroys all active particles and effects. 306 */ 307 reset: function() { 308 this.particles.cleanUp(); 309 this.particleEffects.cleanUp(); 310 } 311 312 }, /** @scope R.particles.ParticleEngine.prototype */{ 313 /** 314 * Get the class name of this object 315 * 316 * @return {String} "R.particles.ParticleEngine" 317 */ 318 getClassName:function () { 319 return "R.particles.ParticleEngine"; 320 } 321 }); 322 323 };