/****************************** File *********************************Author: Rocco BowlingUsage: Generalized particle system generator***************************** License ********************************This code can be freely used as long as this header, in its entirety, is kept with the codeThis code is presented as is. The author takes noresponsibilities for any version of this code.(c) 2006 Rocco Bowling*********************************************************************/#pragma mark *** Includes ***#include "particles.h"#include "main.h"#include "glGeometry.h"#pragma mark *** Definitions ***#pragma mark *** Globals ***static GLuint gBulletinBoardVertexShader;static bool gShaderInited = false;#define random_1()	(random_float() * 2.0f - 1.0f)#pragma mark *** Prototypes ***#pragma mark *** Code ***/** @brief	Allocates and initializes a new particle system generator *   *  @return	A pointer to a structure which defined the *			particle system generator.  This value will *			need to be passed to other particle system *			functions. */ParticleSystemInfo * particles_init(){	ParticleSystemInfo * info = (ParticleSystemInfo *)calloc(sizeof(ParticleSystemInfo), 1);		info->geom_particle_array = mutableDataArray_initWithCapacity(kDefaultParticles,														     	  sizeof(GLColoredTexturedNormalQuad));		info->info_particle_array = mutableDataArray_initWithCapacity(kDefaultParticles,														     	  sizeof(ParticleInfo));														     	  	info->color_stages_array = mutableDataArray_initWithCapacity(1,														     	 sizeof(ColorStageInfo));														     	 	info->wind_areas_array = mutableArray_initWithCapacity(1);		info->attractors_array = mutableArray_initWithCapacity(1);														       	info->spawn_chain_array = mutableDataArray_initWithCapacity(1,																sizeof(SpawnChainInfo));		info->brightness = 1.0;	info->texture_ref = -1;	info->spawn_debt = 0;		if(gShaderInited == false)	{		gShaderInited = shaders_load("Resources/Shaders/ARBvp_billboard.txt", &gBulletinBoardVertexShader);	}		return info;}/** @brief	Deallocates a particle system generator *   *  @param	info	Pointer to the particle system (returned by *					particles_init()). */void particles_destruct(ParticleSystemInfo * info){	mutableDataArray_destruct(info->spawn_chain_array);	mutableArray_destruct(info->wind_areas_array);	mutableArray_destruct(info->attractors_array);	mutableDataArray_destruct(info->color_stages_array);	mutableDataArray_destruct(info->geom_particle_array);	mutableDataArray_destruct(info->info_particle_array);		free(info);}#pragma mark -/** @brief	Spawn chains provide a method for linking multiple particle *			system generators together.  In the case where you want to *			have a fire particle generate a smoke particle after it *			dies, you can use spawn chains to accomplish that. *   *  @param	src_info	The particle system which triggers new particles *  @param	dst_info	The system where new particles will be generated *  @param	s			Number of update frames a particle in the src_info *						system needs to wait before causing a new particle *						to be generated in the dst_info system *  @param  change		[0.0, 1.0] - random factor that a spawn occurs *						 *  @return	A reference value needed to pass to spawn chain functions. */void * particles_addSpawnChain(	ParticleSystemInfo * src_info,								ParticleSystemInfo * dst_info,								int s,								float chance){	SpawnChainInfo a;		a.dst_info = dst_info;	a.stage = s;	a.chance = chance;		return mutableDataArray_addObject(	src_info->spawn_chain_array, &a);}/** @brief	Removes a previously defined spawn chain *   *  @param	info	Pointer to the particle system *  @param	ref		Value returned by particles_addSpawnChain() */void particles_removeSpawnChain(ParticleSystemInfo * info,							   	void * ref){	mutableDataArray_removeObject(	info->spawn_chain_array,									ref);}#pragma mark -/** @brief	Adds a wind deformation area to the particle system.  The area *			is defined by a cylinder, inside which a particle's velocity *			is modified by the wind force (with variation). *   *  @param	info		Pointer to the particle system *  @param	p1			One end of the cylinder *  @param	p2			Other end of the cylinder *  @param	radius		Radius of the cylinder *  @param	wind		Velocity vector of the window *  @param	variation	Variation to be applied to the wind vector *						 *  @return	A reference value needed to pass to wind area functions. */void * particles_addWindArea(	ParticleSystemInfo * info,								float p1[3],								float p2[3],								float radius,								float wind[3],								float variation[3]){	WindAreaInfo a;	float t[3];			VEC_SET(a.p1, p1[0], p1[1], p1[2]);	VEC_SET(a.p2, p2[0], p2[1], p2[2]);	a.radius_sq = radius * radius;		VEC_DIFF(t, p2, p1);	VEC_FAST_LENGTH(a.length_sq, t);			memcpy(a.wind, wind, sizeof(a.wind));	memcpy(a.variation, variation, sizeof(a.variation));		return mutableArray_addObject(	info->wind_areas_array, &a, sizeof(a));}/** @brief	Removes a previously defined wind area *   *  @param	info	Pointer to the particle system *  @param	ref		Value returned by particles_addWindArea() */void particles_removeWindArea( ParticleSystemInfo * info,							   void * ref){	mutableArray_removeObject(	info->wind_areas_array,								ref);}#pragma mark -/** @brief	Attractors attract particles (doh)*  *  @param	info		Pointer to the particle system*  @param	locationOffset	Offset to the location of the particle system to*							to which particles are attracted.  Defined in this*							manner so that you can move the whole particle*							without affecting the general shape of the *							particles.*  @param	radius		Clipping radius at which particles start being attracted*  @param	strength	How strongly particles are attracted*  @param	inner_radius	Particles begin to fade when they are inside the*							inner radius*						*  @return	A reference value needed to pass to attractor functions.*/void * particles_addAttractor(	ParticleSystemInfo * info,								float * locationOffset,								float radius,								float strength,								float inner_radius){	AttractorInfo a;		VEC_COPY(a.locationOffset, locationOffset);	a.radius = radius;	a.strength = strength;	a.inner_radius = inner_radius;			return mutableArray_addObject(info->attractors_array, &a, sizeof(a));}/** @brief	Removes a previously defined particle attractor *   *  @param	info	Pointer to the particle system *  @param	ref		Value returned by particles_addAttractor() */void particles_removeAttractor( ParticleSystemInfo * info,							    void * ref){	mutableArray_removeObject(	info->attractors_array,								ref);}#pragma mark -/** @brief	Set the gravity for the particle system*  *  @param	info	Pointer to the particle system*  @param	x,y,z	Gravity velocity*/void particles_setGravity(ParticleSystemInfo * info, float x, float y, float z){	VEC_SET(info->gravity, x, y, z);}/** @brief	Set the spawn location for new particles*  *  @param	info	Pointer to the particle system*  @param	x,y,z	Location for spawning new particles*/void particles_setLocation(ParticleSystemInfo * info, float x, float y, float z){	VEC_SET(info->location, x, y, z);}/** @brief	Set the variant for the spawn location*  *  @param	info	Pointer to the particle system*  @param	x,y,z	Variant on location for spawning new particles**  n.b.		How variants work.  Variants work by taking a random value in the*			range of [-x, x] and adding that to the spawn location.  If you want*			a line of particle to be generated along the X-axis, you can set the*			location of the center of that line as the spawn x location, and*			then set the variant to 1/2 the length of the total line.*/void particles_setLocationVariant(ParticleSystemInfo * info, float x, float y, float z){	VEC_SET(info->range, x, y, z);}/** @brief	Set the color of new particles*  *  @param	info	Pointer to the particle system*  @param	r,g,b,a	Red, green, blue, alpha colors*/void particles_setColor(ParticleSystemInfo * info, float r, float g, float b, float a){	VEC_SET_4(info->color, r, g, b, a);}/** @brief	Set the color variant of new particles*  *  @param	info	Pointer to the particle system*  @param	r,g,b,a	Red, green, blue, alpha color variants*/void particles_setColorVariant(ParticleSystemInfo * info, float r, float g, float b, float a){	VEC_SET_4(info->colorVariant, r, g, b, a);}/** @brief	Add a color stage to the particles*  *  @param	info		Pointer to the particle system*  @param	s			Number of update frames over which the particle changes*  @param	r,g,b,a		Red, green, blue, alpha destination colors*  @param	vr,vg,vb,va	Red, green, blue, alpha destination color variants*/void particles_setColorStage(ParticleSystemInfo * info, int s, float r, float g, float b, float a, float vr, float vg, float vb, float va){	if(s == 0)	{		particles_setColor(info, r, g, b, a);		particles_setColorVariant(info, vr, vg, vb, va);	}	else	{		ColorStageInfo cinfo = {s, r, g, b, a, vr, vg, vb, va};				mutableDataArray_addObject(	info->color_stages_array,							   		&cinfo);	}}/** @brief	Set the velocity for new particles*  *  @param	info	Pointer to the particle system*  @param	x,y,z	Velocity vector of new particles*/void particles_setVelocity(ParticleSystemInfo * info, float x, float y, float z){	VEC_SET(info->velocity, x, y, z);}/** @brief	Set the velocity variant for new particles*  *  @param	info	Pointer to the particle system*  @param	x,y,z	Velocity variant of new particles*/void particles_setVelocityVariant(ParticleSystemInfo * info, float x, float y, float z){	VEC_SET(info->velocityVariant, x, y, z);}/** @brief	Set the scale for new particles*  *  @param	info	Pointer to the particle system*  @param	x,y,z	Scale of new particles*/void particles_setScale(ParticleSystemInfo * info, float x, float y, float z){	VEC_SET(info->scale, x, y, z);}/** @brief	Set the scale variant for new particles*  *  @param	info	Pointer to the particle system*  @param	x,y,z	Scale variant of new particles*/void particles_setScaleVariant(ParticleSystemInfo * info, float x, float y, float z){	VEC_SET(info->scaleVariant, x, y, z);}/** @brief	Set the spawn rate for new particles*  *  @param	info	Pointer to the particle system*  @param	value	If this value is positive, it defined the number of *					update frames to wait before spawning a new particle.*					If this value is zero, then no new particles will be spawned*					If this value is negative, it defines the number of spawns*					per update frame you want to occur.*/void particles_setRate(ParticleSystemInfo * info, int value){	info->rate = value;	info->rate_counter = info->rate;}/** @brief	Set the rate variant for new particles*  *  @param	info	Pointer to the particle system*  @param	value	Set the rate variant for new particle spawns*/void particles_setRateVariant(ParticleSystemInfo * info, float value){	info->rateVariant = value;}/** @brief	Set the time-to-live (TTL) for new particles*  *  @param	info	Pointer to the particle system*  @param	value	Number of update frames a particle lives for*/void particles_setTTL(ParticleSystemInfo * info, int value){	info->ttl = value;}/** @brief	Set the TTL variant for new particles*  *  @param	info	Pointer to the particle system*  @param	value	Variant number of frames a particle lives for*/void particles_setTTLVariant(ParticleSystemInfo * info, float value){	info->ttlVariant = value;}/** @brief	Set the OpenGL texture name for new particles*  *  @param	info	Pointer to the particle system*  @param	value	OpenGL texture name*/void particles_setTexture(ParticleSystemInfo * info, int value){	info->texture_ref = value;}/** @brief	Set floor Y position and hardness. Simple bounce effect.*  *  @param	info		Pointer to the particle system*  @param	pos			Set the Y coordinate at which the "floor" is*  @param	hardness	Elasticity of particle bounces*/void particles_setFloor(ParticleSystemInfo * info, float pos, float hardness){	info->floor = pos;	info->hardness = hardness;	info->use_floor = true;}/** @brief	The number of currently active particles in this system*  *  @return	The number of currently active particles in this system*/int particles_active(ParticleSystemInfo * info){	return mutableDataArray_count(info->info_particle_array);}/** @brief	Set the animation flag for the particle system*  *  @param info	Pointer to the particle system*/void particles_setAnimate(ParticleSystemInfo * info, bool anim){	info->animation = anim;}/** @brief	Set the initial delay for particles to be spawned*  *  @param info	Pointer to the particle system*  @param wait	Number of update frames to wait before spawning*/void particles_setInitialDelay(ParticleSystemInfo * info, int wait){	info->wait_counter = wait;}/** @brief	Brightness is multiplied by the color values of all particles*  *  @param info			Pointer to the particle system*  @param brightness	[0.0,1.0] brightness value*/void particles_setBrightness(ParticleSystemInfo * info, float brightness){	info->brightness = brightness;}#pragma mark -/** @brief	Fills out a ParticleInfo structure.  Used internally.*  *  @param info		Pointer to the particle system*  @param pinfo		Pointer to a ParticleInfo structure*  @param ginfo		Pointer to the geometry info*  @param location	Location spawn point of the particle (can be NULL)*  @param velocity	Velocity of the new particle if inherited (can be NULL)*/void particles_new_particle(ParticleSystemInfo * info,							ParticleInfo * pinfo,							GLColoredTexturedNormalQuad * ginfo,							float * location,							float * velocity){	ColorStageInfo * cinfo;	SpawnChainInfo * sinfo;		float normal[3];	float scale_v = random_1();		memset(pinfo, 0, sizeof(ParticleInfo));	memset(ginfo, 0, sizeof(GLColoredTexturedNormalQuad));		if(!location)	{		location = info->location;	}	else	{		if(velocity)		{			float l;						VEC_COPY(normal, velocity);			VEC_NORMALIZE(normal);			VEC_LENGTH(l, info->location);						VEC_SCALE(normal, l, normal);						VEC_SUM(location, location, normal);		}		else		{			VEC_SUM(location, location, info->location);		}	}	velocity = info->velocity;		// Generate the geometry for this particle	GLSetQuadNormals(	*ginfo,						-(info->scale[0] + scale_v * info->scaleVariant[0]), -(info->scale[1] + scale_v * info->scaleVariant[1]), 0.0,						 (info->scale[0] + scale_v * info->scaleVariant[0]), -(info->scale[1] + scale_v * info->scaleVariant[1]), 0.0,						 (info->scale[0] + scale_v * info->scaleVariant[0]),  (info->scale[1] + scale_v * info->scaleVariant[1]), 0.0,						-(info->scale[0] + scale_v * info->scaleVariant[0]),  (info->scale[1] + scale_v * info->scaleVariant[1]), 0.0);		GLSetQuadTextures(	*ginfo,						0.0, 0.0,						1.0, 0.0,						1.0, 1.0,						0.0, 1.0);							VEC_SET_4(	pinfo->color,				info->color[0] + random_1() * info->colorVariant[0],				info->color[1] + random_1() * info->colorVariant[1],				info->color[2] + random_1() * info->colorVariant[2],				info->color[3] + random_1() * info->colorVariant[3]);		GLSetQuadColors(	*ginfo,						pinfo->color[0], pinfo->color[1], pinfo->color[2], pinfo->color[3], 						pinfo->color[0], pinfo->color[1], pinfo->color[2], pinfo->color[3], 						pinfo->color[0], pinfo->color[1], pinfo->color[2], pinfo->color[3], 						pinfo->color[0], pinfo->color[1], pinfo->color[2], pinfo->color[3]);		GLTranslateQuad(	*ginfo,						location[0] + random_1() * info->range[0],						location[1] + random_1() * info->range[1],						location[2] + random_1() * info->range[2]);														VEC_COPY(pinfo->scale, info->scale);	VEC_COPY(pinfo->velocity, velocity);		pinfo->velocity[0] += (random_float() * 2.0 - 1.0) * info->velocityVariant[0];	pinfo->velocity[1] += (random_float() * 2.0 - 1.0) * info->velocityVariant[1];	pinfo->velocity[2] += (random_float() * 2.0 - 1.0) * info->velocityVariant[2];		// Handle color stages...	cinfo = (ColorStageInfo *)mutableDataArray_objectAtIndex(info->color_stages_array, 0);	if(cinfo)	{		float color_target[4];				color_target[0] = cinfo->color[0] + random_1() * cinfo->colorVariant[0];		color_target[1] = cinfo->color[1] + random_1() * cinfo->colorVariant[1];		color_target[2] = cinfo->color[2] + random_1() * cinfo->colorVariant[2];		color_target[3] = cinfo->color[3] + random_1() * cinfo->colorVariant[3];				VEC_DIFF_4(pinfo->color_delta, color_target, pinfo->color);		VEC_SCALE_4(pinfo->color_delta, (1.0f / (float)cinfo->ttl), pinfo->color_delta);		pinfo->color_ttl = cinfo->ttl;		pinfo->color_stage = cinfo;	}		// Handle spawn chains...	sinfo = (SpawnChainInfo *)mutableDataArray_objectAtIndex(info->spawn_chain_array, 0);	if(sinfo)	{		pinfo->spawn_ttl = sinfo->stage;		pinfo->spawn_stage = sinfo;	}		pinfo->ttl = info->ttl + (int)(random_1() * info->ttlVariant);}/** @brief	Force a particle to be spawned.*  *  @param info		Pointer to the particle system*  @param location	Location spawn point of the particle (can be NULL)*  @param velocity	Velocity of the new particle if inherited (can be NULL)*/void particle_spawn(ParticleSystemInfo * info,					float location[3],					float velocity[3]){	ParticleInfo pinfo = {0};	GLColoredTexturedNormalQuad ginfo = {0};		particles_new_particle(info, &pinfo, &ginfo, location, velocity);		// Need to spawn a new particle...	mutableDataArray_addObject(	info->geom_particle_array,				   				&ginfo);	mutableDataArray_addObject(info->info_particle_array,							   &pinfo);}/** @brief	Update the particle system by one update frame*  *  @param info		Pointer to the particle system*/void particles_update(ParticleSystemInfo * info){	// Run though all particles, calling the update function...	void * info_enumerator;	void * geom_enumerator;	void * wind_enumerator;	void * attractor_enumerator;	ParticleInfo * pinfo;	GLColoredTexturedNormalQuad * ginfo;	WindAreaInfo * winfo;	AttractorInfo * ainfo;		float r1 = random_1();	float r2 = random_1();	float r3 = random_1();	float r4 = random_1();		if(info->animation == false)	{		return;	}		if(info->wait_counter)	{		info->wait_counter--;		return;	}		info->rate_counter--;	if(info->rate_counter <= 0)	{		int n = 1;				if(info->rate < 0)		{			n = abs(info->rate);		}				info->rate_counter = info->rate + (int)((random_float() * 2.0 - 1.0) * info->rateVariant);				if(info->spawn_debt)		{			if(n > info->spawn_debt)			{				n -= info->spawn_debt;				info->spawn_debt = 0;			}			else			{				info->spawn_debt -= n;				n = 0;			}		}				if(info->rate == 0)		{			n = 0;		}				while(n--)		{			ParticleInfo pinfo = {0};			GLColoredTexturedNormalQuad ginfo = {0};						particles_new_particle(info, &pinfo, &ginfo, 0, 0);						// Need to spawn a new particle...			mutableDataArray_addObject(	info->geom_particle_array,						   				&ginfo);			mutableDataArray_addObject(info->info_particle_array,									   &pinfo);		}	}			// Update all particles...	info_enumerator = mutableDataArray_objectEnumerator(info->info_particle_array);	geom_enumerator = mutableDataArray_objectEnumerator(info->geom_particle_array);	while(	(info_enumerator = mutableDataArray_nextObject(info->info_particle_array, info_enumerator)) &&			(geom_enumerator = mutableDataArray_nextObject(info->geom_particle_array, geom_enumerator)))	{		GLVertex origin;		float location[3];						pinfo = (ParticleInfo *)mutableDataArray_enumeratedData(info->info_particle_array, info_enumerator);		ginfo = (GLColoredTexturedNormalQuad *)mutableDataArray_enumeratedData(info->geom_particle_array, geom_enumerator);				GLGetQuadOrigin(*ginfo, origin);				VEC_SET(location, origin.x, origin.y, origin.z);				if(info->ttl != 0)		{			pinfo->ttl--;			if(pinfo->ttl <= 0)			{				// Particle died... make a new one!				//particles_new_particle(info, pinfo, ginfo);				//info->spawn_debt++;								mutableDataArray_removeObject(info->info_particle_array, pinfo);				mutableDataArray_removeObject(info->geom_particle_array, ginfo);			}		}				VEC_SUM(pinfo->velocity, pinfo->velocity, info->gravity);				// Connect with a wind area?		wind_enumerator = mutableArray_objectEnumerator(info->wind_areas_array);		while(wind_enumerator = mutableArray_nextObject(info->wind_areas_array, wind_enumerator))		{			winfo = (WindAreaInfo*)mutableArray_enumeratedData(info->wind_areas_array, wind_enumerator);						//if(origin is inside our box)			if(winfo)			{				if(isPointInCylinder(	winfo->p1,										winfo->p2,										winfo->length_sq,										winfo->radius_sq,										location) >= 0.0)				{					pinfo->velocity[0] += winfo->wind[0] + r1 * winfo->variation[0];					pinfo->velocity[1] += winfo->wind[1] + r2 * winfo->variation[1];					pinfo->velocity[2] += winfo->wind[2] + r3 * winfo->variation[2];				}			}		}				// Particle attractors?		attractor_enumerator = mutableArray_objectEnumerator(info->attractors_array);		while(attractor_enumerator = mutableArray_nextObject(info->attractors_array, attractor_enumerator))		{			ainfo = (AttractorInfo*)mutableArray_enumeratedData(info->attractors_array, attractor_enumerator);						//if(origin is inside our box)			if(ainfo)			{				float l;				float origin[3];				float vector[3];								VEC_SUM(origin, ainfo->locationOffset, info->location);								VEC_DIFF(vector, origin, location);				VEC_FAST_LENGTH(l, vector);								if(l <= (ainfo->radius * ainfo->radius))				{					VEC_NORMALIZE(vector);					VEC_SCALE(vector, ainfo->strength, vector);										VEC_SUM(pinfo->velocity, pinfo->velocity, vector);										if(ainfo->inner_radius > 0.0)					{						// Modify the alpha of close particles, remove						// particles that hit the "hole"						if(l < ainfo->inner_radius * ainfo->inner_radius)						{							pinfo->color_delta[3] = 0;														pinfo->color[3] = l / (ainfo->inner_radius * ainfo->inner_radius);														if(l < ainfo->radius * ainfo->radius * 0.01)							{								// remove the particle!								pinfo->ttl = 0;							}						}					}				}			}		}						GLTranslateQuad(	*ginfo,							pinfo->velocity[0], pinfo->velocity[1], pinfo->velocity[2]);									if(info->use_floor)		{			// Going down?			if(pinfo->velocity[1] < 0.0)			{				if(origin.y < info->floor)				{					pinfo->velocity[1] = pinfo->velocity[1] * -1.0 * info->hardness;										pinfo->velocity[0] *= info->hardness;					pinfo->velocity[2] *= info->hardness;										GLTranslateQuad(	*ginfo,										0.0, fabs(info->floor - origin.y), 0.0);				}			}		}				// Spawn chaining support...		if(pinfo->spawn_stage)		{			pinfo->spawn_ttl--;			if(pinfo->spawn_ttl <= 0)			{				if(random_float() < pinfo->spawn_stage->chance)				{					// create a new particle...					particle_spawn(	pinfo->spawn_stage->dst_info,									location,									pinfo->velocity);				}								pinfo->spawn_stage = (SpawnChainInfo*)mutableDataArray_nextObject(info->spawn_chain_array, pinfo->spawn_stage);				if(pinfo->spawn_stage)				{					pinfo->spawn_ttl = pinfo->spawn_stage->stage;				}			}		}				// Color transitions...		if(pinfo->color_stage)		{			pinfo->color_ttl--;			if(pinfo->color_ttl <= 0)			{				float color_target[4];								pinfo->color_stage = (ColorStageInfo*)mutableDataArray_nextObject(info->color_stages_array, pinfo->color_stage);				if(!pinfo->color_stage)				{					// reset!					pinfo->color_stage = (ColorStageInfo *)mutableDataArray_objectAtIndex(info->color_stages_array, 0);				}								color_target[0] = pinfo->color_stage->color[0] + r1 * pinfo->color_stage->colorVariant[0];				color_target[1] = pinfo->color_stage->color[1] + r2 * pinfo->color_stage->colorVariant[1];				color_target[2] = pinfo->color_stage->color[2] + r3 * pinfo->color_stage->colorVariant[2];				color_target[3] = pinfo->color_stage->color[3] + r4 * pinfo->color_stage->colorVariant[3];								pinfo->color_ttl = pinfo->color_stage->ttl;								VEC_DIFF_4(pinfo->color_delta, color_target, pinfo->color);				VEC_SCALE_4(pinfo->color_delta, (1.0f / (float)pinfo->color_stage->ttl), pinfo->color_delta);			}			else			{				VEC_SUM_4(pinfo->color, pinfo->color, pinfo->color_delta);								GLSetQuadColors(	*ginfo,									pinfo->color[0]*info->brightness, pinfo->color[1]*info->brightness, pinfo->color[2]*info->brightness, pinfo->color[3], 									pinfo->color[0]*info->brightness, pinfo->color[1]*info->brightness, pinfo->color[2]*info->brightness, pinfo->color[3], 									pinfo->color[0]*info->brightness, pinfo->color[1]*info->brightness, pinfo->color[2]*info->brightness, pinfo->color[3], 									pinfo->color[0]*info->brightness, pinfo->color[1]*info->brightness, pinfo->color[2]*info->brightness, pinfo->color[3]);			}		}	}}/** @brief	Render the particle system*  *  @param info		Pointer to the particle system*/void particles_render(ParticleSystemInfo * info){	int style = 0;		if(info->wait_counter)	{		return;	}		getPreference("Shader Group", &style);	if(style != 0 && use_vertex_programs == 0)	{		setPreference("Shader Group", 0);		style = 0;	}		if(info->texture_ref >= 0)	{		glTexture_setTexture(gTxtMgrRef, info->texture_ref);	}			switch(style)	{				case 0:		// No shaders			{				float right[3];				float up[3];				float mat[16];				GLColoredTexturedNormalQuad * quad = (GLColoredTexturedNormalQuad *)mutableDataArray_bytes(info->geom_particle_array);				long count = mutableDataArray_count(info->geom_particle_array);				float alpha = 1.0;								glGetFloatv(GL_MODELVIEW_MATRIX, mat);								VEC_SET(right, mat[0], mat[4], mat[8]);				VEC_SET(up, mat[1], mat[5], mat[9]);								glColor3f(1.0, 0.0, 0.0);								glBegin(GL_QUADS);								while(count--)				{					glTexCoord2d(quad->at.u, quad->at.v);					glColor4f(quad->ac.r, quad->ac.g, quad->ac.b, quad->ac.a * alpha);					glVertex3f(	quad->a.x + right[0] * quad->an.x + up[0] * quad->an.y,								quad->a.y + right[1] * quad->an.x + up[1] * quad->an.y,								quad->a.z + right[2] * quad->an.x + up[2] * quad->an.y);										glTexCoord2d(quad->bt.u, quad->bt.v);					glColor4f(quad->bc.r, quad->bc.g, quad->bc.b, quad->bc.a * alpha);					glVertex3f(	quad->a.x + right[0] * quad->bn.x + up[0] * quad->bn.y,								quad->a.y + right[1] * quad->bn.x + up[1] * quad->bn.y,								quad->a.z + right[2] * quad->bn.x + up[2] * quad->bn.y);										glTexCoord2d(quad->ct.u, quad->ct.v);					glColor4f(quad->cc.r, quad->cc.g, quad->cc.b, quad->cc.a * alpha);					glVertex3f(	quad->a.x + right[0] * quad->cn.x + up[0] * quad->cn.y,								quad->a.y + right[1] * quad->cn.x + up[1] * quad->cn.y,								quad->a.z + right[2] * quad->cn.x + up[2] * quad->cn.y);													glTexCoord2d(quad->dt.u, quad->dt.v);					glColor4f(quad->dc.r, quad->dc.g, quad->dc.b, quad->dc.a * alpha);					glVertex3f(	quad->a.x + right[0] * quad->dn.x + up[0] * quad->dn.y,								quad->a.y + right[1] * quad->dn.x + up[1] * quad->dn.y,								quad->a.z + right[2] * quad->dn.x + up[2] * quad->dn.y);													quad++;				}								glEnd();			}			break;				case 1:		// Billboarding shader												glInterleavedArrays(GL_T2F_C4F_N3F_V3F, 0, mutableDataArray_bytes(info->geom_particle_array));							glDrawArrays( GL_QUADS,						  0,						  mutableDataArray_count(info->geom_particle_array) * 4);									break;	}		if(info->texture_ref >= 0)	{		glTexture_endTexture(gTxtMgrRef, info->texture_ref);	}}/** @brief	Set up state for rendering the particle system*  */void particle_pre_render(){	int style;		getPreference("Shader Group", &style);	if(style != 0 && use_vertex_programs == 0)	{		setPreference("Shader Group", 0);		style = 0;	}		if(style == 1)	{		glBindProgramARB(GL_VERTEX_PROGRAM_ARB, gBulletinBoardVertexShader);		glEnable(GL_VERTEX_PROGRAM_ARB);				glEnableClientState(GL_COLOR_ARRAY);		glEnableClientState(GL_TEXTURE_COORD_ARRAY);		glEnableClientState(GL_NORMAL_ARRAY);		glEnableClientState(GL_VERTEX_ARRAY);				glProgramLocalParameter4fARB(GL_VERTEX_PROGRAM_ARB, 0, 1.0, 1.0, 1.0, 1.0);	}}/** @brief	Tear down state for rendering the particle system*  */void particle_post_render(){	int style;		getPreference("Shader Group", &style);	if(style != 0 && use_vertex_programs == 0)	{		setPreference("Shader Group", 0);		style = 0;	}		if(style == 1)	{		glDisableClientState(GL_VERTEX_ARRAY);		glDisableClientState(GL_NORMAL_ARRAY);		glDisableClientState(GL_TEXTURE_COORD_ARRAY);		glDisableClientState(GL_COLOR_ARRAY);				glDisable(GL_VERTEX_PROGRAM_ARB);	}}