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 };