-
Notifications
You must be signed in to change notification settings - Fork 2
Position and movement
In the Particle Fire Simulation Revision positioning of each particle is done using Cartesian coordinate system. Even though the program itself uses SDL-library, the concepts presented here are not depended on the graphics library. Any graphics library, that has ability to do pixel access, can be used.
So let's get going. Here is an graph of coordinates used in this program:
Both the Y-axis (the line in the middle that goes from top to bottom) and the X-axis (the line in the middle that goes from left to right) are in range of 1 to -1 where the topmost value of Y-axis’s is 1 and the rightmost value is X-axis’s is 1. While -1 being Y-axis’s bottom-most value and X-axis’s leftmost value.
To further understand how each particle is positioned on the screen using these coordinates, this example will demonstrate how twenty particles are positioned in random positions. This can be achieved by using following algorithm for setting position to both Y- and X-axis:
//Random position for X-axis:
double particle_xpos = ((2.0 * rand()) / RAND_MAX) - 1;
//Random position for Y-axis:
double particle_ypos = ((2.0 * rand()) / RAND_MAX) - 1;
Where rand is a pseudo-random integral value between 0 and 32767, thus RAND_MAX being the maximum value 32767. In short random number between 0 and 32767 is divided by 32767. After that the result is multiplied by 2.0 so that the final outcome is converted to floating point numbers and then one last -1 so that the values range on 1 and -1 according the coordinate system presented above. Here is an example of of twenty particle object’s (elements ranging from 0 to 19) position calculated with above algorithm:
Particle # | X-axis | Y-axis |
---|---|---|
0 | 0.881324 | 0.0454907 |
1 | -0.641283 | 0.918335 |
2 | 0.806671 | -0.941154 |
3 | -0.141915 | 0.273706 |
4 | -0.201072 | 0.40221 |
5 | 0.776629 | 0.448826 |
6 | -0.497708 | -0.82714 |
7 | 0.22043 | 0.314566 |
8 | 0.286918 | 0.880185 |
9 | -0.693276 | -0.391165 |
10 | -0.715079 | -0.851693 |
11 | -0.494808 | 0.0400419 |
12 | -0.0897969 | -0.306577 |
13 | 0.216423 | -0.523715 |
14 | 0.274763 | 0.94553 |
15 | -0.461073 | 0.393439 |
16 | -0.00897887 | -0.102355 |
17 | 0.311774 | -0.202308 |
18 | -0.0435098 | -0.830141 |
19 | -0.928602 | 0.755418 |
As can be seen none of the values go over the 1 to -1 range. To further demonstrate how each of the particles are set, here is an graph that displays their position on the coordinate system:
Now, to change these coordinate positions to pixel position on the screen one more algorithm needs to be executed:
int pixel_x = (particle_xpos + 1) * SCREEN_WIDTH / 2;
int pixel_y = (particle_ypos + 1) * SCREEN_HEIGHT / 2;
Let’s assume that resolution width is 1280 and height 720, when previous coordinates are put through previous algorithm values are changed to following:
Particle # | X-axis | Y-axis |
---|---|---|
0 | 76 | 376 |
1 | 230 | 691 |
2 | 1156 | 21 |
3 | 549 | 459 |
4 | 511 | 505 |
5 | 1137 | 522 |
6 | 321 | 62 |
7 | 781 | 473 |
8 | 824 | 677 |
9 | 196 | 219 |
10 | 182 | 53 |
11 | 323 | 374 |
12 | 583 | 250 |
13 | 779 | 171 |
14 | 816 | 700 |
15 | 345 | 502 |
16 | 634 | 323 |
17 | 840 | 287 |
18 | 612 | 61 |
19 | 46 | 632 |
So how the coordinate to resolution values algorithm works? First, 1 is added to values in both variable particle_xpos and particle_xpos, because when turning coordinates from range of 1 to -1 to resolution pixel values, there cannot be negative numbers, since those values would go out of the screen. With this little addition values are changed so that they are converted to range of 2 to 0. After that they are multiplied by screen width or height depending on the axis and finally divided by two to again make sure that the values are within resolution range.
For last example, going over how the whole positioning works, by selecting one random number between 0 and 32767, generator gave me number 12226. Let's say that this is the x-axis of resolution of 1920 in width and 1080 in height. Value 12226 is then divided by RAND_MAX 32767, which equals to 0.3731192968535417. This is then multiplied by 2 resulting 0.7462385937070833. Finally we reduce it by 1 resulting -0.2537614062929167. This is the coordinate on the Cartesian coordinate system. To change this to resolution value, 1 is added solution of first algorithm resulting again 0.7462385937070833. This is then multiplied by 1920 resulting 1432.7780999176 and finally divided by 2, which in turn is 716.3890499588. Since value is stored in the integer variable the decimals after the dot are discarded and the final value is 716 in x-axis. Now the same can be repeated to Y-axis, but of course that would need another random number and the conversion to pixel is done with 1080 rather than 1920.
Understanding how particles are placed on the coordinate system, so how about making particles move utilizing the very same system? This can be done by adding variables to horizontal and vertical movement of particles. Basically when movement is done particle’s position value is changed so subtly that it seems to human eye that it appears to be moving from one part of the screen to another. What is really happening is that when new frame is drawn on the screen. Think of it as drawing an animation on couple of pages on a flipbook where the pages change so suddenly that it seems like movement. Like with flipbook, we are at the basics of animation here with particle movement also. The particle X- and Y- axis position is ever slightly changed so that it is seems like smooth movement when the screen updated in 30 or 60 times per second. Algorithm for calculating new position begins by getting values by which the particle is moving, the so called speed:
//Calculate the movement speed on X-axis
particle_xSpeed = 0.001 * (((2.0 * rand())/RAND_MAX) -1);
//Calculate the movement speed on Y-axis
particle_ySpeed = 0.001 * (((2.0 * rand())/RAND_MAX) -1);
There is one part (2.0 * rand())/RAND_MAX) -1) that is the same algorithm as setting the position of particle, this is done because otherwise values would be totally different and the particle’s movement would not smooth, but rather uneven, even jumping from top of the screen to the bottom in one millisecond or being out of bounds of the grid. New position must still be inside the 1 to -1 range that is set on the coordinate system used here. To set a new particle position relatively close to the old one, the speed algorithm has multiplication by 0.001 on both axises.
Here is an example speeds of of twenty particle objects (elements ranging from 0 to 19) calculated with algorithm presented previously:
Particle # | X-axis | Y-axis |
---|---|---|
0 | 0.00113254 | 0.000878026 |
1 | 0.00197642 | 0.000266854 |
2 | -0.00199384 | -0.000478084 |
3 | 0.00176537 | 0.00162978 |
4 | 0.0018651 | 0.00160843 |
5 | 0.00141587 | 0.000594453 |
6 | -0.000914855 | -0.00129146 |
7 | 0.00184443 | 0.00186402 |
8 | 0.00101401 | 0.000623127 |
9 | -0.00111002 | 0.00043069 |
10 | -0.00195261 | -0.000935308 |
11 | -0.00130553 | -0.00146449 |
12 | -0.00185605 | 0.00109067 |
13 | -0.000314882 | 0.000458249 |
14 | 0.00116679 | -0.000440001 |
15 | -0.000304019 | 0.00107844 |
16 | -0.000298437 | 0.00131639 |
17 | -0.000252924 | 0.00148801 |
18 | -0.0014473 | -0.00113466 |
19 | -0.00059915 | 0.000740988 |
As can be seen these values are rather small, but that is really the purpose as said, rapid movement doesn’t look smooth to human eye. But how these speed values are used to move the particles around? Answer is simply adding them to particle’s position like so:
//Set new position on the screen for X-axis
particle_xpos += particle_xSpeed;
//Set new position on the screen for Y-axis
particle_ypos += particle_ySpeed;
To demonstrate this, particle #8 have selected, which is positioned at 0.286918 on x-axis and at 0.880185 on y-axis. For speed particle #2’s speed have been selected for this example. So particle #8 will travel at speed of -0.00199384 on x-axis and at -0.000478084 y-axis. Here is what it looks like on table when the position is upgraded 19 times:
Update # | X-axis | Y-axis |
---|---|---|
1 | 0.286918 | 0.880185 |
2 | 0.28492416 | 0.879706916 |
3 | 0.28293032 | 0.879228832 |
4 | 0.28093648 | 0.878750748 |
5 | 0.27894264 | 0.878272664 |
6 | 0.2769488 | 0.87779458 |
7 | 0.27495496 | 0.877316496 |
8 | 0.27296112 | 0.876838412 |
9 | 0.27096728 | 0.876360328 |
10 | 0.26897344 | 0.875882244 |
11 | 0.2669796 | 0.87540416 |
12 | 0.26498576 | 0.874926076 |
13 | 0.26299192 | 0.874447992 |
14 | 0.26099808 | 0.873969908 |
15 | 0.25900424 | 0.873491824 |
16 | 0.2570104 | 0.87301374 |
17 | 0.25501656 | 0.872535656 |
18 | 0.25302272 | 0.872057572 |
19 | 0.25102888 | 0.871579488 |
20 | 0.24903504 | 0.871101404 |
Here is that same pattern in graph format, starting from top right and final update being on the lower left:
Even if this works and it can be used, doesn’t mean it is completely efficient. In next header, class for particle will be build and make the code even more elegant. Do also notice, that if there is nothing removing the old particle position from the screen after updating the position then rather than single particle moving from one position to another, a line is drawn like in the example grid above. There are many methods for doing that and one of them is blurring that is also presented on this program. Another possible solution is also to use graphics library function to draw a blank screen after every position change.
According to what we have learned so far, a particle class would look like this:
class Particle {
//Data members
private:
double particle_xpos;
double particle_ypos;
double particle_xSpeed;
double particle_ySpeed;
//Data methods
public:
//Constructor that sets particle in random position and defines random speed for them
Particle(){
particle_xpos = ((2.0 * rand()) / RAND_MAX) - 1;
particle_ypos = ((2.0 * rand()) / RAND_MAX) - 1;
particle_xSpeed = 0.001 * (((2.0 * rand()) / RAND_MAX) - 1);
particle_ySpeed = 0.001 * (((2.0 * rand()) / RAND_MAX) - 1);
}
//Data method that updates particle position according the speed value
void update(){
particle_xpos += particle_xSpeed;
particle_ypos += particle_ySpeed;
}
}; //end of class definition
When an object is created from this class, a constructor sets values to random using the same algorithms described on previous header. After that each movement of the particle can be done with update data method, and like above it can be done 19 times or as long as particle is no longer even visible on the coordinate grid. To keep this code simple there is nothing preventing values going over 1 or -1, but implementing such “guard” is out of scope of this example. Now because updating both the X and Y-axis speed using seperate speed algorithms is not efficient, there is a way to make one separate variable for speed and another for direction. But how can be degrees used for setting direction?
Answer for that is to set direction is using something called radian, it is a system where 360 degrees is same as 2 multiplied by pi. Since pi is 3.14159265358979323846 then the 6.28318530717958647692 equals to 360 degrees in radian system. More about how radian works can be found here. Radian system is used in mathematical situations like this because it is more flexible than just using regular degrees. Algorithm for random direction for each particle would be:
particle_direction = (2 * M_PI * rand()) / RAND_MAX
Where M_PI is value of pi with 20 digits as stated above, rand() and RAND_MAX the same as before when defining random values for position and speed. And yet again here is 20 particle objects with each their own directions calculated with this algorithm and for reference also presented in degrees:
Particle # | Radians | Degrees |
---|---|---|
0 | 3.47482 | 199.09252057° |
1 | 1.59662 | 91.479587486° |
2 | 6.1747 | 353.78424976° |
3 | 6.05705 | 347.0434013° |
4 | 1.83014 | 104.85929792° |
5 | 3.01972 | 173.01721131° |
6 | 1.65155 | 94.626844655° |
7 | 2.99092 | 171.36709286° |
8 | 5.97048 | 342.08330567° |
9 | 3.73754 | 214.14526776° |
10 | 4.12701 | 236.46025501° |
11 | 2.15034 | 123.20540652° |
12 | 5.42058 | 310.57635651° |
13 | 5.14657 | 294.87673997° |
14 | 3.18864 | 182.69561439° |
15 | 3.31029 | 189.66564596° |
16 | 1.86675 | 106.95689641° |
17 | 3.4352 | 196.82246178° |
18 | 2.83417 | 162.38597942° |
19 | 0.100518 | 5.7592571651° |
For example particle #0 is travelling to 3.47482 radians which is 199.09252057 degrees. This can calculated by this function: degrees = α(radians) × 180 degrees / pi. So how are these radians converted into travelling motion on the screen? First updating particle class to include radian directions:
class Particle {
//Data members
private:
double particle_xpos;
double particle_ypos;
double particle_speed;
double particle_direction;
//Data methods
public:
//Constructor that sets particle in random position and defines random speed and direction for them
Particle(){
particle_xpos = ((2.0 * rand()) / RAND_MAX) - 1;
particle_ypos = ((2.0 * rand()) / RAND_MAX) - 1;
particle_direction = (2 * M_PI * rand()) / RAND_MAX;
particle_speed = (0.001 * rand()) / RAND_MAX;
}
//Data method that updates particle position according the speed value
void update(){
double calculateXspeed = particle_speed * cos(particle_direction);
double calculateYspeed = particle_speed * sin(particle_direction);
particle_xpos += calculateXspeed;
particle_ypos += calculateYspeed;
}
}; //end of class definition
Going over how the new update class works. Instead of having two different data members for speed in X- and Y-axis now there is single data member for speed and one for direction. And since there is no longer need to use negative speed values to go backwards on a grid, -1 can be also discarded from the previous algorithm. Update method has changed from just updating particle position depending on the speed data member, to calculating how fast particle is moving in relation to both x- and x-axis. To further demonstrate cos and sin functions work and what values do they return, here is picture for starters: To recap the center point is at (X:0, Y:0). The circle drawn on picture above has a radius of 1, so the rightmost point of the circle is (X:1, Y:0), the top is (X:0, Y:1), the leftmost is (X:-1, Y:0), and the bottom is (X:0, Y:-1). Do notice however, that this graph differs from Cartesian coordinate system graph earlier, since in this graph’s center point represents a particle’s center, rather than center of a screen. Also do remember, that when using unit circle 0 rad is at rightmost position and that also these rules of thumb apply:
- 0 degrees or 0 rad is at rightmost position, in coordinates X:1 and Y:0.
- 90 degrees or 0.5 * pi rad is at topmost position, in coordinates X:0 and Y:1.
- 180 degrees or pi rad is at leftmost position, in coordinates X:-1 and Y:0.
- 270 degrees or 1.5 * pi rad is at bottom-most position, in coordinates X:0 and Y:-1.
- After full circle 360 degrees is 2 * pi rad, again at rightmost position and coordinates X:1 and Y:0.
So 0.5 * pi, pi ..etc. are in relation to rightmost position on x-axis. So what does functions cos and sin do? In short they return the sine and cosine of an angle of radians inputted to them in range of 1 to -1. Cos is used to calculate x-axis and sin to y-axis from radian. If we take the values from rules of thumb above and convert them to angles here are the results in same order:
- cos( 0 ) = 1, sin( 0 ) = 0
- cos( 0.5 * pi ) = 0, sin( 0.5 * pi ) = 1
- cos( pi ) = -1, sin( pi ) = 0
- cos( 1.5 * pi ) = 0, sin( 1.5 * pi ) = -1
- cos( 2 * pi ) = 1, sin( 2 * pi ) = 0
To further illustrate this, let's continue using particle_direction data member with a value of 3.47482 radian, which is 199.09252057 degrees. With this value cos(particle_direction) returns value -0.94499162 on X-axis and sin(particle_direction) returns value -0.32709454 on Y-axis. Positioning these values inside the coordinates it would mean that particle would move to left and slightly down from its current position:
Here is an example run of very fast (with 0.01 multiplier to speed and random generated number of 24864) of the particle moving to 3.47482 radian, and for the sake of an example, from middle of the of the grid (position X:0, Y:0).
Update # | X-axis | Y-axis |
---|---|---|
0 | 0 | 0 |
1 | -0.007170711886862 | -0.002482033339201 |
2 | -0.014341423773724 | -0.004964066678402 |
3 | -0.021512135660585 | -0.007446100017603 |
4 | -0.028682847547447 | -0.009928133356804 |
5 | -0.035853559434309 | -0.012410166696005 |
6 | -0.043024271321171 | -0.014892200035206 |
7 | -0.050194983208033 | -0.017374233374407 |
8 | -0.057365695094894 | -0.019856266713608 |
9 | -0.064536406981756 | -0.022338300052809 |
10 | -0.071707118868618 | -0.02482033339201 |
11 | -0.07887783075548 | -0.027302366731211 |
12 | -0.086048542642342 | -0.029784400070412 |
13 | -0.093219254529203 | -0.032266433409613 |
14 | -0.100389966416065 | -0.034748466748814 |
15 | -0.107560678302927 | -0.037230500088015 |
16 | -0.114731390189789 | -0.039712533427216 |
17 | -0.12190210207665 | -0.042194566766418 |
18 | -0.129072813963512 | -0.044676600105619 |
19 | -0.136243525850374 | -0.04715863344482 |
20 | -0.143414237737236 | -0.049640666784021 |
And finally example in drawn to grid, in this example grid is coordinate graph that displays where the particle moves on the screen::
To see how this is implemented on this project please check out Particle.h and Particle.cpp on this wiki or straight from source code at repo page.