1 /*
  2   Copyright (c) 2008 Seneca College
  3   Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/)
  4 */
  5 
  6 
  7 /**
  8 	@class ParticleSystem is used to simulate phenomena such as fire, smoke, rain, etc.
  9 */
 10 c3dl.ParticleSystem = function()
 11 {
 12 	// particle uv's won't change so instead of keeping
 13 	// a copy of uv coords in each particle, keep one copy
 14 	// in the particle system.
 15 	this.particleUVs = [1,1,	//
 16 						1,0,	//
 17 						0,0,	//
 18 						0,1];	//
 19 
 20 	// winding order of these verts is counter-clockwise, the same as models. This
 21 	// prevents having to change the winding order state in openGL when rendering.
 22 	this.billboardVerts =[	 1,-1, 0,	// bottom right
 23 							 1, 1, 0,	// top right
 24 							-1, 1, 0,	// top left
 25 							-1,-1, 0];	// bottom left
 26 
 27 	// this particle system's transformation matrix
 28 	this.mat = [1,0,0,0,
 29 				0,1,0,0,
 30 				0,0,1,0,
 31 				0,0,0,1];
 32 
 33 	// list of the Particle objects.
 34 	this.particles;
 35 
 36 	// keep a count of the number of dead particles we have 
 37 	// this turns some operations from O(n) to O(1).
 38 	this.numDeadParticles;
 39 
 40 	// every particle in this sytem will have the same texture
 41 	this.texture;
 42 
 43 	// velocity range of the particles.  When particles
 44 	// are born they are assigned a range between these two
 45 	// values.
 46 	this.minVelocity = [0,0,0];
 47 	this.maxVelocity = [0,0,0];
 48   
 49   this.maxAngVel = 0;
 50   this.minAngVel = 0;
 51 
 52 	// lifetime range of the particles in seconds. Once the
 53 	// age of a particle has surpassed its lifetime, the particle
 54 	// is no longer updated and rendered.
 55 	this.minLifetime = 0;
 56 	this.maxLifetime = 0;
 57 
 58 	// the color range of the particles. The color also contains an
 59 	// alpha component.
 60 	this.minColor = [0,0,0,0];
 61 	this.maxColor = [0,0,0,0];
 62   
 63   //
 64   //
 65   this.minSize = 1;
 66   this.maxSize = 1;
 67 
 68 	// acceleration is a property of the subsystem. Every
 69 	// particle in the subsystem will share the same
 70 	// acceleration.
 71 	this.acceleration = [0,0,0,0];
 72 	
 73 	// blend modes
 74 	this.dstBlend = c3dl.ZERO;
 75 	this.srcBlend = c3dl.ZERO;
 76 
 77 	this.blendEq = c3dl.FUNC_ADD;
 78 
 79 	// we need the camera vectors to create a billboard.
 80 	// on each update, we check if our local values are the
 81 	// same as the scene's camera. if the scene's camera 
 82 	// was updated, we recalculate our billboard.
 83 	this.camUp = [0,0,0];
 84 	this.camLeft = [0,0,0];
 85 	this.camDir = [0,0,0];
 86 
 87 	// if the particle system is playing then the particles are
 88 	// updated and rendered. Otherwise they are
 89 	this.isPlaying = false;
 90 
 91 	// how many particles are emitted per second?
 92 	this.emitRate = 0;
 93 
 94 	// these are used to calculate how many particles to emit on update.
 95 	this.timeCounter = 0;
 96 	this.isTimeCounterSetup = false;
 97 
 98 	// this will be passed to the renderer
 99 	this.particleVerts = null;
100 	this.particleColors = null;
101 	this.particleTexCoords = null;
102 
103 	// VBOS
104 	this.VBOVertices = null;
105 	this.VBOColors = null;
106 	this.VBOTexCoords = null;
107 	this.firstTimeRender = true;
108 
109 	/**
110 		Emit a number of particles all at once. If the amount of particles 
111 		to emit exceeds the amount which are available, the last remaining
112 		particles are emitted.  If there are no particles available to emit,
113 		none are emitted.
114 
115 		@param numToEmit
116 	*/
117 	this.emit = function(numToEmit)
118 	{
119 		// only emit particles if the user passed in a valid number
120 		// and there is actually space left to emit.
121 		// If we have a dead particle, that means we can recycle it.
122 		if( numToEmit <= 0 || this.numDeadParticles == 0)
123 		{
124 			return;
125 		}
126 
127 		// if we tried to emit more particles than there is enough
128 		// space for, just emit the last remaining particles, since
129 		// our array is static, there is nothing we can do except wait
130 		// for particles to die off.
131 		//
132 		// cap the amount of particles to emit.
133 		numToEmit = (numToEmit > this.numDeadParticles) ? this.numDeadParticles: numToEmit;
134 
135 		// stay within the bounds and don't emit more than we have.
136 		// we count down until numParticlesToEmit is zero.
137 		for (var i = 0; i < this.particles.length && numToEmit > 0; i++)
138 		{
139 			// if we found a dead particle, recycle it.
140 			if( this.particles[i].isAlive() == false)
141 			{
142 				// emit the particle at index i which is recyclable.
143 				this.emitParticle(i);
144 
145 				// one less we have to emit
146 				numToEmit--;
147 			}
148 		}
149 	}
150 
151 	/**
152 		@private
153 		Emit a particle at index 'index'.
154 
155 		@param index
156 	*/
157 	this.emitParticle = function(index)
158 	{
159 		if( index >= 0 && index < this.particles.length)
160 		{
161 			this.particles[index].setVelocity([
162 											c3dl.getRandom(this.minVelocity[0],this.maxVelocity[0]),
163 											c3dl.getRandom(this.minVelocity[1],this.maxVelocity[1]),
164 											c3dl.getRandom(this.minVelocity[2],this.maxVelocity[2])
165 											]);
166 
167 			this.particles[index].setAge(0);
168 			this.particles[index].setLifetime(c3dl.getRandom(this.minLifetime, this.maxLifetime));
169 			this.particles[index].setAlive(true);
170 
171 			// when the particle is emitted, we assign it the position of the particle system
172 			// By doing this, moving the particle system will not move the particles.
173 			this.particles[index].setPosition([this.mat[12],this.mat[13],this.mat[14]]);
174 
175 			this.particles[index].setColor([
176                       c3dl.getRandom(this.minColor[0], this.maxColor[0]),
177 											c3dl.getRandom(this.minColor[1], this.maxColor[1]),
178 											c3dl.getRandom(this.minColor[2], this.maxColor[2]),
179                       c3dl.getRandom(this.minColor[3], this.maxColor[3]),
180 											]);
181 
182       this.particles[index].setSize(c3dl.getRandom(this.minSize, this.maxSize));
183 
184 			// we now have one less particle to recycle.
185 			this.numDeadParticles--;
186 		}
187 	}
188 
189 
190 	/**
191 		Initialize the subsystem.  This must be called before the particle 
192 		system is actually used.
193 
194 		@param {integer} numParticles
195 	*/
196 	this.init = function(numParticles)
197 	{
198 		// allocate once since allocation is expensive. We will just recycle the 
199 		// particles when they die.
200 		this.particles = new Array(numParticles);
201 		
202 		for (var i = 0; i < numParticles; i++)
203 		{
204 			this.particles[i] = new c3dl.Particle();
205 		}
206 
207 		this.particleVerts = new Array(this.particles.length * 3 * 4);
208 		this.particleColors = new Array(this.particles.length * 4 * 4);
209         this.particleTexCoords = new Array(this.particles.length * 2 * 4 );
210     
211         for(var i = 0; i < this.particleColors.length; i++)
212         {
213             this.particleColors[i] = 0.0;
214         }
215         for(var i = 0; i < this.particleVerts.length; i++)
216         {
217             this.particleVerts[i] = 0.0;
218         }
219         for(var i = 0; i < this.particleTexCoords.length; i++)
220         {
221             this.particleTexCoords[i] = 0;
222         }
223 
224 		// fix this depending on emission
225 		this.isPlaying = true;
226 		this.numDeadParticles = this.particles.length;
227 	}
228 
229 	/**
230 		@private
231 		is the subsystem ready to render?
232 		
233 		@return true if the subsystem is ready to render.
234 	*/
235 	this.isReady = function()
236 	{
237 		return (this.particles instanceof Array);
238 	}
239 
240 	/**
241 		Get the total amount of particles in the system, including dead ones.
242     
243     return {int}
244 	*/
245 	this.getNumParticles = function()
246 	{
247 		return this.particles.length;
248 	}
249 	
250 	/**
251 		Get the particle at index i.
252 		
253 		@param {int} i the index of the particle to get.
254 	*/
255 	this.getParticle = function(i)
256 	{
257 		if( i >= 0 && i < this.particles.length)
258     {
259       return this.particles[i];
260     }     
261 	}
262 	
263 	/**
264 		@private
265 	*/
266 	this.getVertices = function()
267 	{
268 		return this.billboardVerts;
269 	}
270 	
271 	/**
272 		@private
273 	*/
274 	this.getTexCoords = function()
275 	{
276 		return this.particleUVs;
277 	}
278 
279 	/**
280 		@private
281 		Removed the particle at index 'index' from being update and rendered.
282 		
283 		@param 
284 	*/
285 	this.killParticle = function(index)
286 	{
287 		if(index > 0 && index < this.particles.length)
288 		{
289 			this.particles[index].setAlive(false);
290 			this.numDeadParticles++;
291 		}
292 	}
293 
294 	/**
295 		Set the amount of particles to emit per second. To stop emission, pass in zero.
296 
297 		@param particlesPerSecond
298 	*/
299 	this.setEmitRate = function(particlesPerSecond)
300 	{
301 		if(particlesPerSecond == 0)
302 		{
303 			this.emitRate = 0;
304 			this.isTimeCounterSetup = false;
305 		}
306 		else if( particlesPerSecond > 0)
307 		{
308 			this.emitRate = particlesPerSecond;
309 		}
310 	}
311 
312 
313 	/**
314 		@private
315 		testing funciton
316 	*/
317 	this.translate = function(vec)
318 	{
319 		this.mat[12] += vec[0];
320 		this.mat[13] += vec[1];
321 		this.mat[14] += vec[2];
322 	}
323 
324 	/**
325 		Set the position of the particle system.
326 		
327 		@param {Array} 
328 	*/
329 	this.setPosition = function(vec)
330 	{
331 		this.mat[12] = vec[0];
332 		this.mat[13] = vec[1];
333 		this.mat[14] = vec[2];
334 	}
335 
336 
337 	/**
338 		Get the acceleration of all the particles.  This is usually 
339 		gravity, but does not have to be.
340 	*/
341 	this.getAcceleration = function()
342 	{
343 		return this.acceleration;
344 	}
345 
346 	/**
347 		Get blend equation
348 	*/
349 	this.getBlendEquation = function()
350 	{
351 		return this.blendEq;
352 	}
353 
354 	/**
355 		get the desination blend factor.
356 		
357 		@return {int} the destination blend factor.
358 	*/
359 	this.getDstBlend = function()
360 	{
361 		return this.dstBlend;
362 	}
363 
364 	/**
365 		Get the maximum color range.  The max color range 
366 		is an array of components which each contain the
367 		maximum value each component can be assigned. components
368 		range from 0 to 1.
369 
370 		@returns {Array}
371 	*/
372 	this.getMaxColor = function()
373 	{
374 		return this.maxColor;
375 	}
376 
377 	/**
378 		Get the minimum color range.
379 		@returns {Array}
380 	*/
381 	this.getMinColor = function()
382 	{
383 		return this.minColor;
384 	}
385 
386 	/**
387 		Get the maximum number of seconds for which any particle can live.
388 
389 		@return the maximum number of seconds for which any particle 
390 		can live.
391 	*/
392 	this.getMaxLifetime = function()
393 	{
394 		return this.maxLifetime;
395 	}
396 
397 	/**
398 		Get the minimum amount of seconds for which any particle can live.
399 
400 		@return the minimum amount of seconds for which any particle can live.
401 	*/
402 	this.getMinLifetime = function()
403 	{
404 		return this.minLifetime;
405 	}
406 
407 	/**
408 	*/
409 	this.getMinVelocity = function()
410 	{
411 		return this.minVelocity;
412 	}
413 
414 	/**
415 		Get the maximum values for each xyz component any particle can 
416 		be assigned when emitted.
417 
418 		@return {Array} the maximum values for each xyz component any particle can 
419 		be assigned when emitted.
420 	*/
421 	this.getMaxVelocity = function()
422 	{
423 		return this.maxVelocity;
424 	}
425 
426 	/**
427 		Get the texture which all particles are assigned.
428 
429 		@return {String} the texture which all particles are assigned.
430 	*/
431 	this.getTexture = function()
432 	{
433 		return this.texture;
434 	}
435 
436 	/**
437 		Get the source blending factor.
438 		
439 		@return {int} the source blending factor.
440 	*/
441 	this.getSrcBlend = function()
442 	{
443 		return this.srcBlend;
444 	}
445 
446 	/**
447 		Set the acceleration of this subsystem. This is commonly gravity, but can be
448 		any valid vector.
449 
450 		@param {Array} acceleration
451 	*/
452 	this.setAcceleration = function(acceleration)
453 	{
454 		if(c3dl.isValidVector(acceleration))
455 		{
456 			this.acceleration = acceleration;
457 		}
458 	}
459 	
460 	/**
461 		Set the destination blend factor. parameter must be one of:
462 		c3dl.ZERO,
463 		c3dl.ONE,
464 		c3dl.SRC_COLOR,
465 		c3dl.ONE_MINUS_SRC_COLOR,
466 		c3dl.SRC_ALPHA,	
467 		c3dl.ONE_MINUS_SRC_ALPHA,
468 		c3dl.DST_ALPHA,
469 		c3dl.ONE_MINUS_DST_ALPHA,
470 		c3dl.DST_COLOR,
471 		c3dl.ONE_MINUS_DST_COLOR or
472 		c3dl.SRC_ALPHA_SATURATE
473 
474 		@param {int} dstBlend
475 	*/
476 	this.setDstBlend = function(dstBlend)
477 	{
478 		switch(dstBlend)
479 		{
480 			case c3dl.ZERO:		case c3dl.ONE:
481 			case c3dl.SRC_COLOR:	case c3dl.ONE_MINUS_SRC_COLOR:
482 			case c3dl.SRC_ALPHA:	case c3dl.ONE_MINUS_SRC_ALPHA:
483 			case c3dl.DST_ALPHA:	case c3dl.ONE_MINUS_DST_ALPHA:
484 			case c3dl.DST_COLOR:	case c3dl.ONE_MINUS_DST_COLOR:
485 			case c3dl.SRC_ALPHA_SATURATE:
486 				this.dstBlend = dstBlend;
487 				break;
488 		}		
489 	}
490 
491 	/**
492 		Set the maximum color range a particle can be assigned.  Each component
493 		must range from 0 to 1 inclusive.
494 		
495 		@param {Array} maxColor - array of 4 floating point values ranging from 0 to 1 inclusive.
496 	*/
497 	this.setMaxColor = function(maxColor)
498 	{
499 		if(c3dl.isValidColor(maxColor))
500 		{
501 			this.maxColor = maxColor;
502 		}
503 	}
504 
505 	/**
506 		Set the minimum color values each particle can be.
507 
508 		@param {Array} minParticleColor the minimum Color values each particle 
509 		can be.
510 	*/
511 	this.setMinColor = function(minColor)
512 	{
513 		if(c3dl.isValidColor(minColor))
514 		{
515 			this.minColor = minColor;
516 		}
517 	}
518 
519 	/**
520 		Set the maximum number of seconds for which any particle can live.
521 		Value must be greater than zero.
522 
523 		@param {float} maxLifetime
524 	*/
525 	this.setMaxLifetime = function(maxLifetime)
526 	{
527 		if(maxLifetime > 0)
528 		{
529 			this.maxLifetime = maxLifetime;
530 		}
531 	}
532 
533 	/**
534 		Set the minimum amount of seconds for which particles will live.
535 		Value must be greater than zero.
536 
537 		@param minParticleLifetime the minimum amount of seconds for 
538 		which the particles can live.
539 	*/
540 	this.setMinLifetime = function(minLifetime)
541 	{
542 		if(minLifetime > 0)
543 		{
544 			this.minLifetime = minLifetime;
545 		}
546 	}
547   
548   /**
549   
550   */
551   this.setMaxSize = function(maxSize)
552   {
553     if(maxSize > 0)
554     {
555       this.maxSize = maxSize;
556     }
557   }
558 
559   /**
560     
561   */
562   this.setMinSize = function(minSize)
563   {
564     if(minSize > 0)
565     {
566       this.minSize = minSize;
567     }
568   }
569 
570 	/**
571 		Set the minimum velocity of all the particles.
572 
573 		@param {Array} minVelocity the minimum velocity of all the particles.
574 	*/
575 	this.setMinVelocity = function(minVelocity)
576 	{
577 		if(c3dl.isValidVector(minVelocity))
578 		{
579 			this.minVelocity = minVelocity;
580 		}
581 	}
582 
583 	/**
584 		Set the maximum velocity of all the particles.
585 
586 		@param {Array} maxVelocity the maximum velocity of all the particles.
587 	*/
588 	this.setMaxVelocity = function(maxVelocity)
589 	{
590 		if(c3dl.isValidVector(maxVelocity))
591 		{
592 			this.maxVelocity = maxVelocity;
593 		}
594 	}
595 	
596   /**
597   */
598   this.setMaxAngularVelocity = function(maxAngVel)
599   {
600     
601   }
602   
603   /**
604   */
605   this.setMinAngularVelocity = function(minAngVel)
606   {
607   }
608   
609 	/**
610 		(src * srcBlend) Eq (dst * dstBlend)
611 		
612 		@param {int} blenEq
613 	*/
614 	this.setBlendEquation = function(blendEq)
615 	{
616 		switch(blendEq)
617 		{
618 			case c3dl.FUNC_ADD:
619 			case c3dl.FUNC_SUBTRACT:
620 			case c3dl.FUNC_REVERSE_SUBTRACT:
621 				this.blendEq = blendEq;
622 				break;
623 		}
624 	}
625 
626 	
627 	/**
628 		Set the Source blending factor.  parameter must be one of:
629 		c3dl.ZERO,
630 		c3dl.ONE,
631 		c3dl.SRC_COLOR,
632 		c3dl.ONE_MINUS_SRC_COLOR,
633 		c3dl.SRC_ALPHA,	
634 		c3dl.ONE_MINUS_SRC_ALPHA,
635 		c3dl.DST_ALPHA,
636 		c3dl.ONE_MINUS_DST_ALPHA,
637 		c3dl.DST_COLOR,
638 		c3dl.ONE_MINUS_DST_COLOR or
639 		c3dl.SRC_ALPHA_SATURATE
640 
641 		@param {int} srcBlend
642 	*/
643 	this.setSrcBlend = function(srcBlend)
644 	{
645 		switch(srcBlend)
646 		{
647 			case c3dl.ZERO:			case c3dl.ONE:
648 			case c3dl.SRC_COLOR:		case c3dl.ONE_MINUS_SRC_COLOR:
649 			case c3dl.SRC_ALPHA:		case c3dl.ONE_MINUS_SRC_ALPHA:
650 			case c3dl.DST_ALPHA:		case c3dl.ONE_MINUS_DST_ALPHA:
651 			case c3dl.DST_COLOR:		case c3dl.ONE_MINUS_DST_COLOR:
652 			case c3dl.SRC_ALPHA_SATURATE:
653 				this.srcBlend = srcBlend;
654 				break;
655 		}
656 	}
657 
658 
659 	/**
660 		Set the texure of the particles.
661 
662 		@param {String} textureName
663 	*/
664 	this.setTexture = function(textureName)
665 	{
666 		this.texture = textureName;
667 	}
668 
669 	/**
670 		@private
671 		Update the positions of the particles if they are alive.  Also,
672 		calculate how many particles to emit if the emit rate has been
673 		set.
674 
675 		@param {float} timeStep
676 	*/
677 	this.update = function(timeStep)
678 	{        
679 		// only calculate how many to emit if we actually want to 
680 		// emit particles
681 		if( this.emitRate > 0)
682 		{
683 			// get the time
684 			if(this.isTimeCounterSetup == false)
685 			{
686 				this.timeCounter = timeStep;
687 				this.isTimeCounterSetup = true;
688 			}
689 			else
690 			{
691 				this.timeCounter += timeStep;
692 			}
693 
694 			// The user supplies the amount of particles to emit per second since people are
695 			// more accustomed to using seconds than milliseconds.  However timeStep is in 
696 			// milliseconds, so we calculate how many to emit in 1000 milliseconds.
697 			var numToEmit = this.timeCounter * this.emitRate / 1000.0;
698 
699 			// if enough time has elapsed to emit at least one particle, emit however
700 			// many particles.  Otherwise we will have to wait until next update and
701 			// check again until we can emit at least one.
702 			if( numToEmit >= 1 )
703 			{
704 				// numToEmit may be a float, but emit should only be passed
705 				// an integer
706 				this.emit(numToEmit);
707 
708 				// subtract time from the timeCounter to prevent too
709 				// many paritcles from being emitted on the next update
710 				this.timeCounter -= numToEmit  / this.emitRate * 1000.0;
711 			}
712 		}
713 
714     var p = 0, j = 0;
715     for(var i = 0; i < this.particleColors.length; i++, j++)
716     {
717       if(i!=0 && i%16 ==0){
718         p++
719        // c3dl.debug.logWarning(p + "   " + this.particles[p].getColor());
720       }
721       
722       if(j > 3){j=0;}
723       this.particleColors[i] = this.particles[p].getColor()[j];
724     }
725 //     c3dl.debug.logWarning(p + "  .   " + this.particleColors);
726         
727 		// now update the particles
728 		for(var i = 0; i < this.particles.length; i++)
729 		{
730 			// don't update the particle unless its alive.
731 			if(this.particles[i].isAlive())
732 			{
733 				var timeInSeconds = timeStep/1000;
734 
735 				// make shorter variable names to prevent clutter.
736 				var pos = this.particles[i].getPosition();
737 				var vel = this.particles[i].getVelocity();
738 				
739 				this.particles[i].translate([
740 										(vel[0] * timeInSeconds) + this.acceleration[0] * timeInSeconds * timeInSeconds * 0.5,
741 										(vel[1] * timeInSeconds) + this.acceleration[1] * timeInSeconds * timeInSeconds * 0.5,
742 										(vel[2] * timeInSeconds) + this.acceleration[2] * timeInSeconds * timeInSeconds * 0.5
743 											]);
744 
745 				//
746 				for(var p = 0, j=0; p < 12; p++,j++)
747 				{
748 					if(j > 2){j=0;}
749 					this.particleVerts[i*12+p] = this.particles[i].getPosition()[j] + this.getVertices()[p];
750 				}
751         
752 
753 
754 				// update the velocity
755 				this.particles[i].setVelocity([
756 												vel[0] + (this.acceleration[0] * timeInSeconds),
757 												vel[1] + (this.acceleration[1] * timeInSeconds),
758 												vel[2] + (this.acceleration[2] * timeInSeconds)
759 											]);
760 
761 				// Age the particle
762 				this.particles[i].setAge( this.particles[i].getAge() + timeInSeconds);
763 
764 				// kill the particle if it went past its lifetime. If the particle
765 				// is dead, it won't be updated or rendered until it is recycled.
766 				if( this.particles[i].getAge() > this.particles[i].getLifetime() )
767 				{
768 					this.killParticle(i);
769 				}
770 			}
771 		}
772 	}
773 	
774 	this.getVBOTexCoords = function()
775 	{
776 		return this.VBOTexCoords;
777 	}
778 
779 	/**
780 	*/
781 	this.getVBOVertices = function()
782 	{
783 		return this.VBOVertices;
784 	}
785 	
786 	/**
787 	*/
788 	this.getVBOColors = function()
789 	{
790 		return this.VBOColors;
791 	}
792 	
793 	/**
794 		@private
795 		prepare to render the particles. This includes turning off lighting, enabling blending, etc.
796 		
797 		@param glCanvas3D
798 		@param {Scene} scene
799 	*/
800 	this.preRender = function(glCanvas3D,scene)
801 	{
802 		if(this.firstTimeRender === true)
803 		{
804 			for(var i = 0, j = 0; i < this.particleTexCoords.length; i++, j++)
805 			{
806 				if(j > 7){ j= 0;}
807 				this.particleTexCoords[i] = this.particleUVs[j];				
808 			}
809 
810       this.VBOColors = glCanvas3D.createBuffer();
811       glCanvas3D.bindBuffer(glCanvas3D.ARRAY_BUFFER, this.VBOColors);
812       glCanvas3D.bufferData(glCanvas3D.ARRAY_BUFFER, new WebGLFloatArray(this.particleColors), glCanvas3D.STREAM_DRAW);
813 
814 			this.VBOVertices = glCanvas3D.createBuffer();
815 			glCanvas3D.bindBuffer(glCanvas3D.ARRAY_BUFFER, this.VBOVertices);
816 			glCanvas3D.bufferData(glCanvas3D.ARRAY_BUFFER, new WebGLFloatArray(this.particleVerts), glCanvas3D.STREAM_DRAW);
817 
818 			this.VBOTexCoords = glCanvas3D.createBuffer();
819 			glCanvas3D.bindBuffer(glCanvas3D.ARRAY_BUFFER, this.VBOTexCoords);
820 			glCanvas3D.bufferData(glCanvas3D.ARRAY_BUFFER, new WebGLFloatArray(this.particleTexCoords), glCanvas3D.STREAM_DRAW);
821 	
822 			this.firstTimeRender = 0;
823 		}
824 		else
825 		{
826 			glCanvas3D.bindBuffer(glCanvas3D.ARRAY_BUFFER, this.VBOColors);
827 			glCanvas3D.bufferData(glCanvas3D.ARRAY_BUFFER, new WebGLFloatArray(this.particleColors), glCanvas3D.STREAM_DRAW);
828 
829 			glCanvas3D.bindBuffer(glCanvas3D.ARRAY_BUFFER, this.VBOVertices);
830 			glCanvas3D.bufferData(glCanvas3D.ARRAY_BUFFER, new WebGLFloatArray(this.particleVerts), glCanvas3D.STREAM_DRAW);
831 
832 			glCanvas3D.bindBuffer(glCanvas3D.ARRAY_BUFFER, this.VBOTexCoords);
833 			glCanvas3D.bufferData(glCanvas3D.ARRAY_BUFFER, new WebGLFloatArray(this.particleTexCoords), glCanvas3D.STREAM_DRAW);
834 		}
835 
836 		// disable writing into the depth buffer. This will prevent
837 		// the corners of texture overlapping each other.
838 		glCanvas3D.depthMask(false);
839 		
840 		// blending is expensive, so only enable for things that need it.
841 		glCanvas3D.enable(glCanvas3D.BLEND);
842 		
843 		// 
844 		glCanvas3D.blendEquation(this.blendEq);
845 		
846 		//
847 		glCanvas3D.blendFunc(this.getSrcBlend(), this.getDstBlend());
848 	}
849 
850 	/**
851 		@private
852 		Re-enable the depth testing, lighting, etc.
853 		
854 		@param glCanvas3D
855 		@param {Scene} scene
856 	*/
857 	this.postRender = function(glCanvas3D,scene)
858 	{
859 		// blending is expensive so turn it off when not needed.
860 		glCanvas3D.disable(glCanvas3D.BLEND);
861 		glCanvas3D.depthMask(true);
862 	}
863 
864 	/**
865 		@private
866 		Draw all the particles which are alive.
867 		
868 		@param glCanvas3D
869 		@param {Scene} scene
870 	*/
871 	this.render = function(glCanvas3D,scene)
872 	{		
873 		//
874 		this.recalculateBillboard(glCanvas3D, scene);
875 
876 		this.preRender(glCanvas3D,scene);
877 
878 		scene.getRenderer().renderParticleSystem(this);
879 
880 		this.postRender(glCanvas3D,scene);
881 	}
882 	
883 	/**
884 	*/
885 	this.getObjectType = function()
886 	{
887 		return c3dl.PARTICLE_SYSTEM;
888 	}
889 
890 
891 	/**
892 		@private
893 	*/
894 	this.recalculateBillboard = function(glCanvas3D, scene)
895 	{
896 		// if any of the vectors have changed, we have
897 		// to recalculate the billboard.
898 
899 		// if they are equal we don't have to do any more
900 		if( !(c3dl.isVectorEqual(this.camUp, scene.getCamera().getUp()) &&
901 			c3dl.isVectorEqual(this.camLeft, scene.getCamera().getLeft()) &&
902 			c3dl.isVectorEqual(this.camDir, scene.getCamera().getDir()) ))
903 		{
904 			// get local copies of the camera vectors.
905 			this.camUp = scene.getCamera().getUp();
906 			this.camLeft = scene.getCamera().getLeft();
907 			this.camDir =  scene.getCamera().getDir();
908 
909 			var camRight = [-this.camLeft[0],-this.camLeft[1],-this.camLeft[2]];
910 
911 			var bottomRight = c3dl.subtractVectors(camRight, this.camUp);
912 			var bottomLeft = c3dl.subtractVectors(this.camLeft, this.camUp);
913 			var topLeft = c3dl.addVectors(this.camLeft, this.camUp);
914 			var topRight = c3dl.addVectors(camRight, this.camUp);
915 
916 			// use counter clockwise order since models vertices are also counter clockwise.
917 			// This prevents having to change the openGL state of the winding order when 
918 			// switching between rendering models and particle systems.			
919 			this.billboardVerts = [	bottomRight[0],	bottomRight[1],	bottomRight [2],
920 									topRight[0],	topRight[1],	topRight[2],
921 									topLeft[0],		topLeft[1],		topLeft[2],
922 									bottomLeft[0],	bottomLeft[1],	bottomLeft[2]];
923 		}
924 	}
925 }
926