How-To: Create A Simple OpenGL 2D Particle Fountain in C++

I was recently asked about some particle systems I’d put together, so in response, this isn’t really a “look what I’ve coded” post – instead, it’s more of a “this is an easy way to set up a particle system framework” post.

To demonstrate the setup of a particle system using OpenGL, I’ve put together some simple starter code which displays a 2D particle fountain. I was going to just dump this code as a zipped project into a reply, but then (as my wife pointed out) it would be pretty much buried in the comments, so if other people starting OpenGL coding wanted to learn the basics of particle systems my code wouldn’t be as visible. As such, I’ve made this into a separate post – and all our example code does is this:

It’s a dirt-simple effect, but what it does isn’t so important right now – it’s deliberately simple. How it does what it does is what we’ll talk about.

Looking at particle systems from a high level – you generally have to choose between two different approaches to the system as a whole:

  1. You have a fixed number of particles (i.e. you have a fixed size array of however many particles) and you reset each particle to “recycle” it, or
  2. You have a dynamic number of particles (i.e. you have a dynamically resizable array) and you destroy each particle and create a new one as required.

I’ve gone with the latter approach for this code using a vector of particles (nothing to do with Vec2/Vec3 math – just the name of resizable array kinda construct). Admittedly, there’s more overhead in the creation and destruction of particles, but on the upside you don’t get that initial rush of particles you get when using fixed size arrays and as soon as you start the program BLAM! All your particles going at once.

To demonstrate what I mean, in the video below I’ve modified the code to instantiate ALL particles up to the particle limit at once, and then as soon as a particle goes off the bottom of the screen it gets destroyed and a new particle is created. The effect of which is that you get a single big burst of particles, and then as they all get destroyed and recreated at different times they turn into a smooth flow within a couple of seconds:

If you’re using a fixed size particle array, you’ll need to implement some mechanism to delay the instantiation or “launch” of the particles – for example, you might give each particle a random framesUntilLaunch value and decrease it by 1 each frame until it gets to 0 and you can let the particle do its thing. I wrote such a delay system for some fireworks code I did a while back if you’d like to see a concrete example.

Anyways, back at this code, our main is using three main classes to encapsulate data and provide methods to manipulate it:

  • Colour4f.hpp – A class to store a colour as red/green/blue/alpha floating point values and manipulate ’em (including interpolation of colours),
  • Vec2.hpp – A templatised class to store two values as a vector (Not a resizable array this time! An actually “euclidian vector” – i.e. two values which represent a direction and magnitude). It also includes lots of overloaded operators so you can add two vectors together (“up” + “right” gives you “up-right” etc.), multiply a vector by a scalar (i.e. moving “up-right” multiplied by 10 means you’re now moving up-right ten times as fast). By templatised, I mean that you can create a Vec2 of ints, or floats, or doubles, or shorts, or whatever numeric type makes sense for your application – take a look at the source below if you want examples, and finally
  • Particle2D.hpp – A particle class which uses the above Colour4f and Vec2 classes to provide powerful movement and colour modification options in a small & easy to use package.

You can download the complete source code as a Code::Blocks project here, if you’d like: PointSprite_Particle_Fountain_2D.zip.

Note: As my OS of choice is GNU/Linux (LMDE, to be specific), the source files provided will have Linux line-endings – so you’ll need to open them with a good text editor like Notepad++ if you’re in in Windows, otherwise each file will look like a single massive block of noise!

Or, if you’d prefer to just browse the source code so you can copy and paste sections, you’ll find it all laid out below.

I love particle systems – you can do some visually stunning things with really simple code, or you can do amazing things if you take it further. Either way, I hope you have a lot of fun with them (there’s lots more particle stuff on this site if you’re looking for inspiration – try Actionscript tag for a start!) – and if you make anything cool or pretty using and you think I’ve helped – please do show me or let me know – I’ve love to see or hear about what you’ve done! =D

Also – if you make this – show me how, okay?

Cheers!

Main.cpp

  1. #include <iostream>
  2. #include <string>
  3. #include <time.h>
  4. #include <vector>
  5.  
  6. #include <GL/glew.h>      // Include the extension wrangler to have access to enhanced OpenGL features, like pointsprites
  7. #include <GL/glfw.h>      // Include OpenGL Framework library
  8.  
  9. #include "Particle2D.hpp" // Include our custom Particle2D class
  10.  
  11. #define ILUT_USE_OPENGL	  // This MUST be defined before calling the DevIL headers or we don't get OpenGL functionality
  12. #include <IL/il.h>
  13. #include <IL/ilu.h>
  14. #include <IL/ilut.h>
  15.  
  16. // Specify default namespace for commonly used elements
  17. using std::string;
  18. using std::cout;
  19. using std::endl;
  20. using std::vector;
  21.  
  22. // Specify a limit for the size of our particle vector
  23. const int MAX_PARTICLES = 200;
  24.  
  25. // Define our vector (resizable array) of Particle2D objects
  26. vector<Particle2D> p;
  27.  
  28. GLint windowWidth   = 1280;             // Width of our window
  29. GLint windowHeight  = 720;              // Heightof our window
  30.  
  31. GLint windowMidX    = windowWidth  / 2; // Middle of the window horizontally
  32. GLint windowMidY    = windowHeight / 2; // Middle of the window vertically
  33.  
  34. // Unique identifier for the texture we'll apply to our pointsprites
  35. int textureId;
  36.  
  37. void initGL()
  38. {
  39. 	// ----- Report GLFW an OpenGL version info -----
  40.  
  41. 	// Create pointers to ints so we can get the GLFW and OpenGL versions
  42. 	int *major = new int;
  43. 	int *minor = new int;
  44. 	int *revision = new int;
  45.  
  46. 	glfwGetVersion(major, minor, revision);
  47. 	std::cout << "Using GLFW version " << *major << "." << *minor << "." << *revision << std::endl;
  48.  
  49. 	// Note: We must have created an OpenGL window before we can query the GL version
  50. 	glfwGetGLVersion(major, minor, revision);
  51. 	std::cout << "Using OpenGL version " << *major << "." << *minor << "." << *revision << std::endl;
  52.  
  53. 	delete major;
  54. 	delete minor;
  55. 	delete revision;
  56.  
  57. 	//  ----- Initialise GLEW -----
  58.  
  59. 	GLenum err = glewInit();
  60. 	if (GLEW_OK != err)
  61. 	{
  62. 		std::cout << "GLEW initialisation error: " << glewGetErrorString(err) << std::endl;
  63. 		exit(-1);
  64. 	}
  65. 	std::cout << "GLEW intialised successfully. Using GLEW version: " << glewGetString(GLEW_VERSION) << std::endl;
  66.  
  67. 	// ----- GLFW Settings -----
  68.  
  69. 	glfwSwapInterval(1); // Enable vsync
  70.  
  71. 	// ----- Window and Projection Settings -----
  72.  
  73. 	// Setup our viewport to be the entire size of the window
  74. 	glViewport(0, 0, (GLsizei)windowWidth, (GLsizei)windowHeight);
  75.  
  76. 	// Change to the projection matrix, reset the matrix and set up our projection
  77. 	glMatrixMode(GL_PROJECTION);
  78. 	glLoadIdentity();
  79.  
  80. 	// Specify orthographic projection (2D - no size attentuation) with the origin on the bottom left
  81. 	glOrtho(0, windowWidth, 0, windowHeight, -1, 1);
  82.  
  83. 	glMatrixMode(GL_MODELVIEW);
  84. 	glLoadIdentity();
  85.  
  86. 	// ----- Initialize DevIL libraries -----
  87.  
  88. 	// DevIL sanity check
  89. 	if ( (iluGetInteger(IL_VERSION_NUM) < IL_VERSION) || (iluGetInteger(ILU_VERSION_NUM) < ILU_VERSION) || (ilutGetInteger(ILUT_VERSION_NUM) < ILUT_VERSION) )
  90. 	{
  91. 		std::cout << "DevIL versions are different... Exiting." << std::endl;
  92. 		exit(-1);
  93. 	}
  94. 	else
  95. 	{
  96. 		std::cout << "Devil initialised successfully." << std::endl;
  97. 	}
  98.  
  99. 	// Initialise all DevIL functionality
  100. 	ilInit();
  101. 	iluInit();
  102. 	ilutInit();
  103.  
  104. 	// ----- OpenGL settings -----
  105.  
  106. 	glClearColor(0.0, 0.0f, 0.0f, 1.0f); // Set our clear colour to black, full alpha
  107.  
  108. 	// Enable blending and specify the blending function
  109. 	glEnable(GL_BLEND);
  110. 	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  111.  
  112. 	// Enable point sprites (Note: We need more than basic OpenGL 1.0 for this functionality - so be sure to use GLEW or such)
  113. 	glEnable(GL_POINT_SPRITE);
  114.  
  115. 	// Specify the origin of the point sprite
  116. 	glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_UPPER_LEFT); // Default - only other option is GL_LOWER_LEFT
  117.  
  118. 	glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE);
  119.  
  120. 	// Specify the drawing mode for point sprites
  121. 	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);       // Draw on top of stuff
  122. 	//glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);  // Try this if you like...
  123. 	//glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);   // Or this... not sure exactly how they differ ;-)
  124.  
  125. 	// Enable 2D Textures
  126. 	glEnable(GL_TEXTURE_2D);
  127.  
  128. 	// Specify linear filtering for minification and magnification
  129. 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  130. 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  131.  
  132. 	// Specify that textures are clamped to edges
  133. 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
  134. 	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
  135.  
  136. 	// Load our texture
  137. 	ILstring filename = "star1.png";
  138. 	textureId = ilutGLLoadImage(filename);
  139.  
  140. 	// As we're only using a single texture we can just bind to it here instead of per frame
  141. 	glBindTexture(GL_TEXTURE_2D, textureId);
  142. }
  143.  
  144. // Function to draw our scene
  145. void updateAndDraw()
  146. {
  147. 	// If there are less than MAX_PARTICLES particles in existence, add one more particle to our particle vector
  148. 	if (p.size() < MAX_PARTICLES)
  149. 	{
  150. 		Particle2D temp(windowMidX, windowMidY);
  151. 		p.push_back(temp);
  152. 	}
  153.  
  154. 	// Clear the screen and depth buffer
  155. 	glClear(GL_COLOR_BUFFER_BIT);
  156.  
  157. 	// Reset the modelview matrix
  158. 	glMatrixMode(GL_MODELVIEW);
  159. 	glLoadIdentity();
  160.  
  161. 	// Enable 2D textures and point sprites
  162. 	glEnable(GL_TEXTURE_2D);
  163. 	glEnable(GL_POINT_SPRITE);
  164.  
  165. 	// Iterate over all particles in the vector
  166. 	vector<Particle2D>::iterator i;
  167. 	for (i = p.begin(); i != p.end(); ++i)
  168. 	{
  169. 		// If the particle time to live is more than zero...
  170. 		if (i->getFramesToLive() > 0)
  171. 		{
  172. 			// ...update the particle position position and draw it.
  173. 			i->update();
  174. 			i->draw();
  175. 		}
  176. 		else // // If it's time to destroy the particle...
  177. 		{
  178. 			// ...then remove it from the vector...
  179. 			// NOTE: Calling erase(SOME-ELEMENT) removes the element from the vector and
  180. 			// returns the next element in the vector, even if it's the "one-past-the-last-element"
  181. 			// i.e. "end()" element which signifies the end of the vector.
  182. 			//
  183. 			// It's VERY important to assign this next element back to the iterator 
  184. 			// because we're modyfing the length of the vector inside a loop which goes
  185. 			// until it gets to the end of a vector --- which, ya know, we're actually changing
  186. 			// as we go! If you don't assign the next element back to the iterator when calling
  187. 			// erase - don't be surprised when your code bombs out with a segfault, k? ;-D
  188. 			i = p.erase(i);
  189.  
  190. 			// ...and then add a new particle to replace it!
  191. 			Particle2D tempParticle(windowMidX, windowMidY);
  192. 			p.push_back(tempParticle);
  193. 		}
  194.  
  195. 	} // End of iterator loop
  196.  
  197. 	// Disable 2D textures and point sprites
  198. 	glDisable(GL_POINT_SPRITE);
  199. 	glDisable(GL_TEXTURE_2D);
  200.  
  201. 	// ----- Stop Drawing Stuff! ------
  202.  
  203. 	glfwSwapBuffers(); // Swap the buffers to display the scene (so we don't have to watch it being drawn!)
  204. }
  205.  
  206. // Fire it up...
  207. int main(int argc, char **argv)
  208. {
  209. 	//cout << "Controls: Use WSAD and the mouse to move around!" << endl;
  210.  
  211. 	// Frame counter and window settings variables
  212. 	int redBits    = 8, greenBits = 8,    blueBits    = 8;
  213. 	int alphaBits  = 8, depthBits = 24,   stencilBits = 0;
  214.  
  215. 	// Flag to keep our main loop running
  216. 	bool running = true;
  217.  
  218. 	// Seed random number generator
  219. 	srand(time(NULL));
  220.  
  221. 	// ----- Intialiase GLFW -----
  222.  
  223. 	// Initialise GLFW
  224. 	if (!glfwInit() )
  225. 	{
  226. 		std::cout << "Failed to initialise GLFW!" << endl;
  227. 		glfwTerminate();
  228. 		return -1;
  229. 	}
  230.  
  231. 	// Create a window
  232. 	if( !glfwOpenWindow(windowWidth, windowHeight, redBits, greenBits, blueBits, alphaBits, depthBits, stencilBits, GLFW_WINDOW))
  233. 	{
  234. 		std::cout << "Failed to open window!" << std::endl;
  235. 		glfwTerminate();
  236. 		return -2;
  237. 	}
  238.  
  239. 	// Call our initGL function to set up our OpenGL options
  240. 	initGL();
  241.  
  242. 	// Main loop
  243. 	while (running)
  244. 	{
  245. 		// Draw our scene
  246. 		updateAndDraw();
  247.  
  248. 		// Exit if ESC was pressed or window was closed
  249. 		running = !glfwGetKey(GLFW_KEY_ESC) && glfwGetWindowParam(GLFW_OPENED);
  250. 	}
  251.  
  252. 	// Clean up GLFW and exit
  253. 	glfwTerminate();
  254.  
  255. 	return 0;
  256. }

Particle2D.hpp

  1. #ifndef PARTICLE_2D_HPP
  2. #define PARTICLE_2D_HPP
  3.  
  4. #include "Vec2.hpp"
  5. #include "Colour4f.hpp"
  6.  
  7. class Particle2D
  8. {
  9. 	protected:
  10. 		Vec2<float> location;  // The current location of the particle
  11. 		Vec2<float> speed;     // The current speed of the particle
  12.  
  13. 		Colour4f colour;       // The colour of the particle
  14.  
  15. 		int size;              // The size of the particle i.e. how big the pointsprite will be drawn
  16.  
  17. 		int framesToLive;      // Number of frames before the particle is destroyed
  18.  
  19. 	public:
  20. 		// ---------- Static particle parameters -----------
  21.  
  22. 		static const int FRAMES_TO_LIVE    = 100;
  23.  
  24. 		static const int MIN_PARTICLE_SIZE = 10;
  25. 		static const int MAX_PARTICLE_SIZE = 64;
  26.  
  27. 		static const float X_SPEED_RANGE   = 3.0f;
  28. 		static const float Y_SPEED_RANGE   = 8.0f;
  29.  
  30. 		static const float GRAVITY         = 0.2f;
  31.  
  32. 		// ---------- Constructors -------------------------
  33.  
  34. 		// Constructor that takes an initial position as a Vec2
  35. 		Particle2D(Vec2<float> initialLocation)
  36. 		{
  37. 			location = initialLocation; // Set the particle location
  38.  
  39. 			initialise();
  40. 		}
  41.  
  42. 		// Constructor that takes an initial position as values (instead of a Vec2)
  43.  
  44. 		Particle2D(int xLocation, int yLocation)
  45. 		{
  46. 			location.set(xLocation, yLocation);
  47.  
  48. 			speed.set(Utils::randRange(-X_SPEED_RANGE, X_SPEED_RANGE), Utils::randRange(0.0f, Y_SPEED_RANGE) );
  49.  
  50. 			initialise();
  51. 		}
  52.  
  53. 		// Constructor that takes a position and a speed
  54. 		Particle2D(Vec2<float> initialLocation, Vec2<float> initialSpeed)
  55. 		{
  56. 			location = initialLocation;
  57. 			speed    = initialSpeed;
  58.  
  59. 			initialise();
  60. 		}
  61.  
  62. 		// ---------- Helper Methods and Getters & Setters -------------------------
  63.  
  64. 		// Method to setup other properties of the particle
  65. 		void initialise()
  66. 		{
  67. 			// Set the initial framesToLive count (this counts down to 0 - at 0 the particle is removed)
  68. 			framesToLive = FRAMES_TO_LIVE;
  69.  
  70. 			// Set an entirely random colour (including random alpha value)
  71. 			colour.fullyRandomise();
  72.  
  73. 			// Randomise the size of the particle
  74. 			size = Utils::randRange(MIN_PARTICLE_SIZE, MAX_PARTICLE_SIZE);
  75. 		}
  76.  
  77. 		float getRed()   { return colour.getRed();	 }
  78. 		float getGreen() { return colour.getGreen(); }
  79. 		float getBlue()  { return colour.getBlue();  }
  80. 		float getAlpha() { return colour.getAlpha(); }
  81.  
  82. 		void setColour(const Colour4f &theColour)
  83. 		{
  84. 			colour.setColour(theColour);
  85. 		}
  86.  
  87. 		void interpolateColourWith(const Colour4f &theColour, float theMixFactor)
  88. 		{
  89. 			colour.interpolateWith(theColour, theMixFactor);
  90. 		}
  91.  
  92. 		void setInterpolatedColour(const Colour4f &sourceColour, const Colour4f &destinationColour, float mixFactor)
  93. 		{
  94. 			colour.setInterpolatedColour(sourceColour, destinationColour, mixFactor);
  95. 		}
  96.  
  97. 		Vec2<float> getLocation()  { return location;        }
  98. 		float getXLocation()       { return location.getX(); }
  99. 		float getYLocation()       { return location.getY(); }
  100.  
  101. 		int getSize()              { return size;            }
  102.  
  103. 		int getFramesToLive()      { return framesToLive;    }
  104.  
  105. 		// ---------- Main Methods To Define Particle Behaviour -------------------------
  106.  
  107. 		void update()
  108. 		{
  109. 			// Calculate the new Y speed of the particle
  110. 			speed.setY( speed.getY() - GRAVITY);
  111.  
  112. 			// Update the position of the particle by the speed it's moving at
  113. 			location += speed;
  114.  
  115. 			framesToLive--; // Decrease the frames the particle will live for by 1
  116. 		}
  117.  
  118. 		void draw()
  119. 		{
  120. 			// Set the size of the point and draw it
  121. 			glPointSize(size);
  122. 			glBegin(GL_POINTS);
  123.  
  124. 			// Set the colour and draw the particle
  125. 			glColor4f( colour.getRed(), colour.getGreen(), colour.getBlue(), colour.getAlpha() );
  126. 			glVertex2f(location.getX(), location.getY() );
  127.  
  128. 			glEnd();
  129. 		}
  130. };
  131.  
  132. #endif

Vec2.hpp

  1. #ifndef Vec2_HPP
  2. #define Vec2_HPP
  3.  
  4. #include <iostream>
  5. #include <math.h>
  6. #include <stdlib.h>
  7.  
  8. template <class T> class Vec2
  9. {
  10. 	private:
  11. 		// A Vec2 simply has two properties called x and y
  12. 		T x, y;
  13.  
  14. 	public:
  15.  
  16. 		// ------------ Constructors ------------
  17.  
  18. 		// Default constructor
  19. 		Vec2()
  20. 		{
  21. 			x = y = 0;
  22. 		}
  23.  
  24. 		// Two parameter constructor
  25. 		Vec2(T xValue, T yValue)
  26. 		{
  27. 			x = xValue;
  28. 			y = yValue;
  29. 		}
  30.  
  31. 		// ------------ Getters and setters ------------
  32.  
  33. 		void set(const T &xValue, const T &yValue)
  34. 		{
  35. 			x = xValue;
  36. 			y = yValue;
  37. 		}
  38.  
  39. 		T getX() const { return x; }
  40. 		T getY() const { return y; }
  41.  
  42. 		void setX(const T &xValue)
  43. 		{
  44. 			x = xValue;
  45. 		}
  46.  
  47. 		void setY(const T &yValue)
  48. 		{
  49. 			y = yValue;
  50. 		}
  51.  
  52.  
  53. 		// ------------ Helper methods ------------
  54.  
  55. 		// Method to reset a vector to zero
  56. 		void zero()
  57. 		{
  58. 			x = y = 0;
  59. 		}
  60.  
  61. 		// Method to normalise a vector
  62. 		void normalise()
  63. 		{
  64. 			// Calculate the magnitude of our vector
  65. 			T magnitude = sqrt((x * x) + (y * y));
  66.  
  67. 			// As long as the magnitude isn't zero, divide each element by the magnitude
  68. 			// to get the normalised value between -1 and +1
  69. 			if (magnitude != 0)
  70. 			{
  71. 				x /= magnitude;
  72. 				y /= magnitude;
  73. 			}
  74. 		}
  75.  
  76. 		// Static method to calculate and return the scalar dot product of two vectors
  77. 		//
  78. 		// Note: The dot product of two vectors tell us things about the angle between
  79. 		// the vectors. That is, it tells us if they are pointing in the same direction
  80. 		// (i.e. are they parallel? If so, the dot product will be 1), or if they're
  81. 		// perpendicular (i.e. at 90 degrees to each other) the dot product will be 0,
  82. 		// or if they're pointing in opposite directions then the dot product will be -1.
  83. 		//
  84. 		// Usage example: double foo = Vec2<double>::dotProduct(vectorA, vectorB);
  85. 		static T dotProduct(const Vec2 &vec1, const Vec2 &vec2)
  86. 		{
  87. 			return vec1.x * vec2.x + vec1.y * vec2.y;
  88. 		}
  89.  
  90. 		// Non-static method to calculate and return the scalar dot product of this vector and another vector
  91. 		//
  92. 		// Usage example: double foo = vectorA.dotProduct(vectorB);
  93. 		T dotProduct(const Vec2 &vec) const
  94. 		{
  95. 			return x * vec.x + y * vec.y;
  96. 		}
  97.  
  98. 		// Static method to calculate and return a vector which is the cross product of two vectors
  99. 		//
  100. 		// Note: The cross product is simply a vector which is perpendicular to the plane formed by
  101. 		// the first two vectors. Think of a desk like the one your laptop or keyboard is sitting on.
  102. 		// If you put one pencil pointing directly away from you, and then another pencil pointing to the
  103. 		// right so they form a "L" shape, the vector perpendicular to the plane made by these two pencils
  104. 		// points directly upwards.
  105. 		//
  106. 		// Whether the vector is perpendicularly pointing "up" or "down" depends on the "handedness" of the
  107. 		// coordinate system that you're using.
  108. 		//
  109. 		// Further reading: http://en.wikipedia.org/wiki/Cross_product
  110. 		//
  111. 		// Usage example: Vec2<double> crossVect = Vec2<double>::crossProduct(vectorA, vectorB);
  112. 		/*static Vec2 crossProduct(const Vec2 &vec1, const Vec2 &vec2)
  113. 		{
  114. 			return Vec2(vec1.y * vec2.z - vec1.z * vec2.y, vec1.z * vec2.x - vec1.x * vec2.z, vec1.x * vec2.y - vec1.y * vec2.x);
  115. 		}*/
  116.  
  117. 		// Easy adders
  118. 		void addX(T value)
  119. 		{
  120. 			x += value;
  121. 		}
  122.  
  123. 		void addY(T value)
  124. 		{
  125. 			y += value;
  126. 		}
  127.  
  128. 		// Method to return the distance between two vectors in 3D space
  129. 		//
  130. 		// Note: This is accurate, but not especially fast - depending on your needs you might
  131. 		// like to use the Manhattan Distance instead: http://en.wikipedia.org/wiki/Taxicab_geometry
  132. 		// There's a good discussion of it here: http://stackoverflow.com/questions/3693514/very-fast-3d-distance-check
  133. 		// The gist is, to find if we're within a given distance between two vectors you can use:
  134. 		//
  135. 		// bool within3DManhattanDistance(Vec2 c1, Vec2 c2, float distance)
  136. 		// {
  137. 		//      float dx = abs(c2.x - c1.x);
  138. 		//      if (dx > distance) return false; // too far in x direction
  139. 		//
  140. 		//      float dy = abs(c2.y - c1.y);
  141. 		//      if (dy > distance) return false; // too far in y direction
  142. 		//
  143. 		//      float dz = abs(c2.z - c1.z);
  144. 		//      if (dz > distance) return false; // too far in z direction
  145. 		//
  146. 		//      return true; // we're within the cube
  147. 		// }
  148. 		//
  149. 		// Or to just calculate the straight Manhattan distance you could use:
  150. 		//
  151. 		// float getManhattanDistance(Vec2 c1, Vec2 c2)
  152. 		// {
  153. 		//      float dx = abs(c2.x - c1.x);
  154. 		//      float dy = abs(c2.y - c1.y);
  155. 		//      float dz = abs(c2.z - c1.z);
  156. 		//      return dx+dy+dz;
  157. 		// }
  158. 		//
  159.  
  160. 		T getManhattanDistance(Vec2 v)
  161. 		{
  162. 			T dx = abs(v.x - x);
  163. 			T dy = abs(v.y - y);
  164.  
  165. 			return dx + dy;
  166. 		}
  167.  
  168. 		bool withinManhattanDistance(Vec2 v, T distance)
  169. 		{
  170. 			T dx = abs(v.x - x);
  171. 			T dy = abs(v.y - y);
  172.  
  173. 			return dx + dy < distance;
  174. 		}
  175.  
  176. 		static T getDistance(const Vec2 &v1, const Vec2 &v2)
  177. 		{
  178. 			T dx = v2.x - v1.x;
  179. 			T dy = v2.y - v1.y;
  180. 			return sqrt(dx * dx + dy * dy);
  181.  
  182. 		}
  183.  
  184. 		Vec2<T> getVectorTo(const Vec2 &v)
  185. 		{
  186. 			Vec2<T> temp(v.x - x, v.y - y);
  187.  
  188. 			temp.normalise();
  189.  
  190. 			return temp;
  191. 		}
  192.  
  193. 		// Method to display the vector so you can easily check the values
  194. 		void display()
  195.  
  196. 		{
  197. 			std::cout << "X: " << x << "\t Y: " << y << std::endl;
  198. 		}
  199.  
  200. 		// ------------ Overloaded operators ------------
  201.  
  202. 		// Overloaded addition operator to add Vec2s together
  203. 		Vec2 operator+(const Vec2 &vector) const
  204. 		{
  205. 			return Vec2<T>(x + vector.x, y + vector.y);
  206. 		}
  207.  
  208. 		// Overloaded add and asssign operator to add Vec2s together
  209. 		void operator+=(const Vec2 &vector)
  210. 		{
  211. 			x += vector.x;
  212. 			y += vector.y;
  213. 		}
  214.  
  215. 		// Overloaded subtraction operator to subtract a Vec2 from another Vec2
  216. 		Vec2 operator-(const Vec2 &vector) const
  217. 		{
  218. 			return Vec2<T>(x - vector.x, y - vector.y);
  219. 		}
  220.  
  221. 		// Overloaded subtract and asssign operator to subtract a Vec2 from another Vec2
  222. 		void operator-=(const Vec2 &vector)
  223. 		{
  224. 			x -= vector.x;
  225. 			y -= vector.y;
  226. 		}
  227.  
  228. 		// Overloaded multiplication operator to multiply two Vec2s together
  229. 		Vec2 operator*(const Vec2 &vector) const
  230. 		{
  231. 			return Vec2<T>(x * vector.x, y * vector.y);
  232. 		}
  233.  
  234. 		// Overloaded multiply operator to multiply a vector by a scalar
  235. 		Vec2 operator*(const T &value) const
  236. 		{
  237. 			return Vec2<T>(x * value, y * value);
  238. 		}
  239.  
  240. 		// Overloaded multiply and assign operator to multiply a vector by a scalar
  241. 		void operator*=(const T &value)
  242. 		{
  243. 			x *= value;
  244. 			y *= value;
  245. 		}
  246.  
  247. 		// Overloaded multiply and assign operator to multiply a vector by a scalar
  248. 		void operator*=(const Vec2 & vector)
  249. 		{
  250. 			x *= vector.x;
  251. 			y *= vector.y;
  252. 			//return Vec2<T>(x * vector.x, y * vector.y);
  253. 		}
  254.  
  255. 		// Overloaded multiply operator to multiply a vector by a scalar
  256. 		Vec2 operator/(const T &value) const
  257. 		{
  258. 			return Vec2<T>(x / value, y / value);
  259. 		}
  260.  
  261. 		// Overloaded multiply and assign operator to multiply a vector by a scalar
  262. 		void operator/=(const T &value)
  263. 		{
  264. 			x /= value;
  265. 			y /= value;
  266. 		}
  267. };
  268.  
  269. #endif

Colour4f.hpp

  1. #ifndef COLOUR4F_HPP
  2. #define COLOUR4F_HPP
  3.  
  4. #include "Utils.hpp"
  5.  
  6. class Colour4f
  7. {
  8. 	private:
  9. 		float red, green, blue, alpha;
  10.  
  11. 	public:
  12. 		Colour4f()
  13. 		{
  14. 			red   = 1.0f;
  15. 			green = 1.0f;
  16. 			blue  = 1.0f;
  17. 			alpha = 1.0f;
  18. 		}
  19.  
  20. 		Colour4f(float redValue, float greenValue, float blueValue, float alphaValue = 1.0f)
  21. 		{
  22. 			red   = redValue;
  23. 			green = greenValue;
  24. 			blue  = blueValue;
  25. 			alpha = alphaValue;
  26. 		}
  27.  
  28. 		// Getters
  29. 		float getRed()   { return red;   }
  30. 		float getGreen() { return green; }
  31. 		float getBlue()  { return blue;  }
  32. 		float getAlpha() { return alpha; }
  33.  
  34. 		void setColour(const Colour4f &theColour)
  35. 		{
  36. 			red   = theColour.red;
  37. 			green = theColour.green;
  38. 			blue  = theColour.blue;
  39. 			alpha = theColour.alpha;
  40. 		}
  41.  
  42. 		void fullyRandomise()
  43. 		{
  44. 			red   = Utils::randRange(0.0f, 1.0f);
  45. 			green = Utils::randRange(0.0f, 1.0f);
  46. 			blue  = Utils::randRange(0.0f, 1.0f);
  47. 			alpha = Utils::randRange(0.0f, 1.0f);
  48. 		}
  49.  
  50. 		void opaqueRandomise()
  51. 		{
  52. 			red   = Utils::randRange(0.0f, 1.0f);
  53. 			green = Utils::randRange(0.0f, 1.0f);
  54. 			blue  = Utils::randRange(0.0f, 1.0f);
  55. 			alpha = 1.0f;
  56. 		}
  57.  
  58. 		// Method to interpolate the current colour with another specified colour with relation to a mix factor
  59. 		// 0.0 means don't mix at all, 1.0 means fully replace colour with destination colour
  60. 		void interpolateWith(const Colour4f &destination, const float mixFactor)
  61. 		{
  62. 			red = red * mixFactor + (destination.red * (1.0f - mixFactor));
  63. 			if (red > 1.0f)
  64. 				red = 1.0f;
  65.  
  66. 			green = green * mixFactor + (destination.green * (1.0f - mixFactor));
  67. 			if (green > 1.0f)
  68. 				green = 1.0f;
  69.  
  70. 			blue = blue * mixFactor + (destination.blue * (1.0f - mixFactor));
  71. 			if (blue > 1.0f)
  72. 				blue = 1.0f;
  73.  
  74. 			alpha = alpha * mixFactor + (destination.alpha * (1.0f - mixFactor));
  75. 			if (alpha > 1.0f)
  76. 				alpha = 1.0f;
  77. 		}
  78.  
  79. 		// Method to set the current colour to be somewhere between a source and destination colour based on the mix factor
  80. 		// where 0.0 is fully source, and 1.0 is fully destination. Also caps colour components to 1.0 to avoid crazyness.
  81. 		void setInterpolatedColour(const Colour4f &source, const Colour4f &destination, const float mixFactor)
  82. 		{
  83. 			red = source.red * mixFactor + (destination.red * (1.0f - mixFactor));
  84. 			if (red > 1.0f)
  85. 				red = 1.0f;
  86.  
  87. 			green = source.green * mixFactor + (destination.green * (1.0f - mixFactor));
  88. 			if (green > 1.0f)
  89. 				green = 1.0f;
  90.  
  91. 			blue = source.blue * mixFactor + (destination.blue * (1.0f - mixFactor));
  92. 			if (blue > 1.0f)
  93. 				blue = 1.0f;
  94.  
  95. 			alpha = source.alpha * mixFactor + (destination.alpha * (1.0f - mixFactor));
  96. 			if (alpha > 1.0f)
  97. 				alpha = 1.0f;
  98. 		}
  99.  
  100. };
  101.  
  102. #endif

And last but not least…

Utils.hpp

  1. #ifndef UTILS_HPP
  2. #define UTILS_HPP
  3.  
  4. #include <cstdlib>
  5.  
  6. class Utils
  7. {
  8. 	// Methods to return a random value between ranges
  9. 	// Yes, it probably would make more sense to templatise it, but the integer version
  10. 	// works slightly differenly. Maybe if that was placed first and THEN the templatised
  11. 	// version it would use the specific version for ints but the generic version for all 
  12. 	// other data types - feel free to experiment and let me know how it works out ;-).
  13.  
  14. 	public:
  15. 		static double randRange(double min, double max)
  16. 		{
  17. 			return min + (((double)rand() / (double)RAND_MAX) * (max - min));
  18. 		}
  19.  
  20. 		static float randRange(float min, float max)
  21. 		{
  22. 			return min + (((float)rand() / (float)RAND_MAX) * (max - min));
  23. 		}
  24.  
  25. 		static int randRange(int min, int max)
  26. 		{
  27. 			return ((int)rand() % (max - min + 1)) + min;
  28. 		}
  29.  
  30. };
  31.  
  32. #endif

Done!

2 thoughts on “How-To: Create A Simple OpenGL 2D Particle Fountain in C++”

  1. Hello again,

    Thanks for this post – it’s just what I needed (and thanks to your wife for convincing you to make it a post on its own!)
    I think the problems I was getting were a result of the wrapper libraries abstracting some of the enums a little too well. When I returned to the C++ code, it all worked exactly as you describe above. Happy days again! :)
    If I manage to create anything interesting, I’ll be sure to let you know.

    Thanks again,
    Andy

  2. So much awesomeness it blew my mind!! Thanks so much for sharing (and thanks to your wife for encouraging you to share).

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.