Reaction-Diffusion
Note to Internet Explorer users
About the code
Definition
Coding
float table[2][SCREEN_HEIGHT][SCREEN_WIDTH];
We will use another array to store the results of the computations for the next frame:
float nextTable[2][SCREEN_HEIGHT][SCREEN_WIDTH];
Then we define our constants.
float DA = 1.0f;
float DB = 0.5f;
float f = 0.055f;
float k = 0.062f;
We will have a function to compute the laplacian:
float Laplacian(int tab, int x, int y)
{
Here is the weights matrix:
static const float coef[3][3] =
{
{0.05f, 0.2f, 0.05f},
{0.2f, -1.0f, 0.2f},
{0.05f, 0.2f, 0.05f}
};
We start 2 loops going from -1 to 1, because we want to go one pixel to the left and to the right, and one pixel
float sum = 0;
for (int dy = -1; dy <= 1; dy++)
for (int dx = -1; dx <= 1; dx++)
{
We calculate the coordinates of the pixel, wrapping it around if we get out of the screen.
int posX = (x + dx + SCREEN_WIDTH) % SCREEN_WIDTH;
int posY = (y + dy + SCREEN_HEIGHT) % SCREEN_HEIGHT;
We get the coefficient from the array and add the value of this pixel to the sum.
float c = coef[dy + 1][dx + 1];
sum += c * table[tab][posY][posX];
}
And finally, we return the sum
return sum;
}
An update function will compute the formulas we saw above.
void update()
{
We loop through each pixel:
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
We get the current A and B concentrations:
float A = table[0][y][x];
float B = table[1][y][x];
And we apply the formulas to the nextTable:
nextTable[0][y][x] = A + DA * Laplacian(0, x, y) - A * B * B + f * (1.0f - A);
nextTable[1][y][x] = B + DB * Laplacian(1, x, y) + A * B * B - (k + f) * B;
}
Finally we copy the content of nextTable to table to be ready for the next frame.
memcpy(table, nextTable, SCREEN_WIDTH * SCREEN_HEIGHT * 2 * sizeof(float));
}
In the main() function, we will initialize the table full of A:
// initialize the table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
table[0][y][x] = 1.0f;
table[1][y][x] = 0.0f;
}
With a small square of B at the center:
for (int y = 0; y < 10; ++y)
for (int x = 0; x < 10; ++x)
{
int posX = SCREEN_WIDTH / 2 + x - 5;
int posY = SCREEN_HEIGHT / 2 + y - 5;
table[1][posY][posX] = 1.0f;
}
Then in the main loop we will display the content of the table, using red for A and blue for B:
while (sys.isQuitRequested() == false)
{
// draw the current table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
Color col(table[0][y][x] * 255,
0,
table[1][y][x] * 255);
gfx.setPixel(x, y, col);
}
We handle the system events:
gfx.render();
sys.processEvents();
And finally we call our update function:
// update the table
update();
}
Download source code
Download executable for Windows
As you will see, this program is quite slow.
Timing
float tim = 0;
int cnt = 0;
At the beginning of update() we will start the timer:
sys.StartPerfCounter();
And at the end of this function, we will increase the counter, add the times and display the result at the 2000th
cnt++;
if (cnt < 2000)
tim += sys.StopPerfCounter();
else if (cnt == 2000)
printf("%f\n", tim);
Download source code
Download executable for Windows
The value we get is about 45000 ms.
Using doubles
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
#include "Graphics.h"
#include "System.h"
#include <string.h>
#include <stdlib.h>
#define SCREEN_WIDTH 400
#define SCREEN_HEIGHT 400
double table[2][SCREEN_HEIGHT][SCREEN_WIDTH];
double nextTable[2][SCREEN_HEIGHT][SCREEN_WIDTH];
double DA = 1.0;
double DB = 0.5;
double f = 0.055;
double k = 0.062;
double Laplacian(int tab, int x, int y)
{
static const double coef[3][3] =
{
{0.05, 0.2, 0.05},
{0.2, -1.0, 0.2},
{0.05, 0.2, 0.05}
};
double sum = 0;
for (int dy = -1; dy <= 1; dy++)
for (int dx = -1; dx <= 1; dx++)
{
int posX = (x + dx + SCREEN_WIDTH) % SCREEN_WIDTH;
int posY = (y + dy + SCREEN_HEIGHT) % SCREEN_HEIGHT;
double c = coef[dy + 1][dx + 1];
sum += c * table[tab][posY][posX];
}
return sum;
}
float tim = 0;
int cnt = 0;
void update()
{
sys.StartPerfCounter();
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
double A = table[0][y][x];
double B = table[1][y][x];
nextTable[0][y][x] = A + DA * Laplacian(0, x, y) - A * B * B + f * (1.0 - A);
nextTable[1][y][x] = B + DB * Laplacian(1, x, y) + A * B * B - (k + f) * B;
}
memcpy(table, nextTable, SCREEN_WIDTH * SCREEN_HEIGHT * 2 * sizeof(double));
cnt++;
if (cnt < 2000)
tim += sys.StopPerfCounter();
else if (cnt == 2000)
printf("%f\n", tim);
}
int main(int argc, char* argv[])
{
// init the window
gfx.init("Reaction/Diffusion 2", SCREEN_WIDTH, SCREEN_HEIGHT);
gfx.init2D();
gfx.clearScreen(Color(0, 0, 0, SDL_ALPHA_OPAQUE));
// initialize the table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
table[0][y][x] = 1.0;
table[1][y][x] = 0.0;
}
for (int y = 0; y < 10; ++y)
for (int x = 0; x < 10; ++x)
{
int posX = SCREEN_WIDTH / 2 + x - 5;
int posY = SCREEN_HEIGHT / 2 + y - 5;
table[1][posY][posX] = 1.0;
}
while (sys.isQuitRequested() == false)
{
// draw the current table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
Color col(table[0][y][x] * 255,
0,
table[1][y][x] * 255);
gfx.setPixel(x, y, col);
}
gfx.render();
sys.processEvents();
// update the table
update();
}
gfx.quit();
return EXIT_SUCCESS;
}
Download source code
Download executable for Windows
I always thought that float were a little bit faster than doubles taking into account the memory accesses, but I
Rewriting the laplacian
double Laplacian(int tab, int x, int y)
{
int xp[3];
int yp[3];
for (int i = -1; i <= 1; i++)
{
xp[i + 1] = (x + i + SCREEN_WIDTH) % SCREEN_WIDTH;
yp[i + 1] = (y + i + SCREEN_HEIGHT) % SCREEN_HEIGHT;
}
Then we can compute our sums:
double sum1 = table[tab][yp[0]][xp[0]] +
table[tab][yp[0]][xp[2]] +
table[tab][yp[2]][xp[0]] +
table[tab][yp[2]][xp[2]];
double sum2 = table[tab][yp[0]][xp[1]] +
table[tab][yp[1]][xp[0]] +
table[tab][yp[1]][xp[2]] +
table[tab][yp[2]][xp[1]];
return 0.05 * sum1 + 0.2 * sum2 - table[tab][yp[1]][xp[1]];
}
Download source code
Download executable for Windows
This code takes only 9000 ms.
Avoiding wrapping
double table[2][SCREEN_HEIGHT+2][SCREEN_WIDTH+2];
double nextTable[2][SCREEN_HEIGHT+2][SCREEN_WIDTH+2];
The coordinates in the laplacian are now simply:
for (int i = -1; i <= 1; i++)
{
xp[i + 1] = x + i;
yp[i + 1] = y + i;
}
And we also have to change the loops in the update() function:
for (int y = 1; y < SCREEN_HEIGHT+1; ++y)
for (int x = 1; x < SCREEN_WIDTH+1; ++x)
{
double A = table[0][y][x];
The memcpy():
memcpy(table, nextTable, (SCREEN_WIDTH+2) * (SCREEN_HEIGHT+2) * 2 * sizeof(double));
In the initialisation we need to set the values of nextTable too, to avoid black borders around the image.
// initialize the table
for (int y = 0; y < SCREEN_HEIGHT+2; ++y)
for (int x = 0; x < SCREEN_WIDTH+2; ++x)
{
table[0][y][x] = 1.0;
table[1][y][x] = 0.0;
nextTable[0][y][x] = 1.0;
nextTable[1][y][x] = 0.0;
}
And of course the drawing routine changes too:
// draw the current table
for (int y = 0; y < SCREEN_HEIGHT; ++y)
for (int x = 0; x < SCREEN_WIDTH; ++x)
{
int r = table[0][y+1][x+1] * 255;
int b = table[1][y+1][x+1] * 255;
gfx.setPixel(x, y, Color(r, 0, b));
}
Download source code
Download executable for Windows
This code only needs about 6000 ms for the 2000 first frames.
Rewriting the laplacian (2)
double prod1Table[2][SCREEN_HEIGHT+2][SCREEN_WIDTH+2];
double prod2Table[2][SCREEN_HEIGHT+2][SCREEN_WIDTH+2];
Here is the function we will use to fill these tables:
void product()
{
for (int t = 0; t < 2; t++)
{
for (int y = 0; y < SCREEN_HEIGHT+2; ++y)
for (int x = 1; x < SCREEN_WIDTH+1; ++x)
prod1Table[t][y][x] = table[t][y][x - 1] + 4.0f * table[t][y][x] + table[t][y][x + 1];
for (int y = 1; y < SCREEN_HEIGHT+1; ++y)
for (int x = 1; x < SCREEN_WIDTH+1; ++x)
prod2Table[t][y][x] = prod1Table[t][y - 1][x] + 4.0f * prod1Table[t][y][x] + prod1Table[t][y + 1][x];
}
}
The Laplacian() function now simply uses the result of these calculations. Here we substract the value for the
double Laplacian(int tab, int x, int y)
{
return 0.05 * prod2Table[tab][y][x] - 1.8 * table[tab][y][x];
}
Now we only have to call product() in the update() function before everything else:
void update()
{
sys.StartPerfCounter();
product();
Download source code
Download executable for Windows
This version takes 3000 ms.
Rewriting the main formulas
nextTable[0][y][x] = A + DA * Laplacian(0, x, y) - A * B * B + f * (1.0 - A);
nextTable[1][y][x] = B + DB * Laplacian(1, x, y) + A * B * B - (k + f) * B;
Now that the laplacian is a simple formula, we can replace it in these ones:
nextTable[0][y][x] = A + DA * (0.05 * prod2Table[0][y][x] - 1.8 * table[0][y][x]) - A*B*B + f*(1.0 - A);
nextTable[1][y][x] = B + DB * (0.05 * prod2Table[1][y][x] - 1.8 * table[1][y][x]) + A*B*B - (k + f)*B;
We can replace table[0][y][x] and table[1][y][x] by their values of A and B:
nextTable[0][y][x] = A + DA * (0.05 * prod2Table[0][y][x] - 1.8*A) - A*B*B + f*(1.0 - A);
nextTable[1][y][x] = B + DB * (0.05 * prod2Table[1][y][x] - 1.8*B) + A*B*B - (k + f)*B;
If we expand the parentheses:
nextTable[0][y][x] = A + DA * 0.05 * prod2Table[0][y][x] - 1.8*DA*A - A*B*B + f - f*A;
nextTable[1][y][x] = B + DB * 0.05 * prod2Table[1][y][x] - 1.8*DB*B + A*B*B - k*B - f*B;
Now if we regroup the terms that contains A and B:
nextTable[0][y][x] = 0.05 * DA * prod2Table[0][y][x] - A*B*B + A - f*A - 1.8*DA*A + f;
nextTable[1][y][x] = 0.05 * DB * prod2Table[1][y][x] + A*B*B + B - f*B - 1.8*DB*B - k*B;
Now we factorize A and B:
nextTable[0][y][x] = 0.05 * DA * prod2Table[0][y][x] - A*B*B + (1.0 - f - 1.8*DA) * A + f;
nextTable[1][y][x] = 0.05 * DB * prod2Table[1][y][x] + A*B*B + (1.0 - f - 1.8*DB - k) * B;
Or if we introduce 2 new variables for the factors of A and B:
double ca = 1.0 - f - 1.8 * DA;
double cb = 1.0 - f - 1.8 * DB - k;
nextTable[0][y][x] = 0.05 * DA * prod2Table[0][y][x] - A*B*B + ca * A + f;
nextTable[1][y][x] = 0.05 * DB * prod2Table[1][y][x] + A*B*B + cb * B;
Notice that these 2 variables only use values that are constants across all pixels.
void update()
{
sys.StartPerfCounter();
double ca = 1.0 - f - 1.8 * DA;
double cb = 1.0 - f - 1.8 * DB - k;
product();
for (int y = 1; y < SCREEN_HEIGHT+1; ++y)
for (int x = 1; x < SCREEN_WIDTH+1; ++x)
{
double A = table[0][y][x];
double B = table[1][y][x];
nextTable[0][y][x] = 0.05 * DA * prod2Table[0][y][x] - A * B * B + ca * A + f;
nextTable[1][y][x] = 0.05 * DB * prod2Table[1][y][x] + A * B * B + cb * B;
}
[...]
}
Download source code
Download executable for Windows
This program need around 2600 ms.
Avoiding the memcpy()
double table[2][2][SCREEN_HEIGHT+2][SCREEN_WIDTH+2];
We will use a curT variable to tell which table is the current one:
int curT = 0;
In the product() function only one line will change:
prod1Table[t][y][x] = table[curT][t][y][x - 1] + 4.0f * table[curT][t][y][x] + table[curT][t][y][x + 1];
And the update() function will now look like this:
void update()
{
[...]
for (int y = 1; y < SCREEN_HEIGHT+1; ++y)
for (int x = 1; x < SCREEN_WIDTH+1; ++x)
{
double A = table[curT][0][y][x];
double B = table[curT][1][y][x];
table[1-curT][0][y][x] = 0.05 * DA * prod2Table[0][y][x] - A * B * B + ca * A + f;
table[1-curT][1][y][x] = 0.05 * DB * prod2Table[1][y][x] + A * B * B + cb * B;
}
The memcpy() will be replaced with a simple variable flipping:
curT = 1 - curT;
Download source code
Download executable for Windows
And of course the initialization and the drawing parts changes too but I won't bother you with that.
Putting back the wrapping
void update()
{
double ca = 1.0 - f - 1.8 * DA;
double cb = 1.0 - f - 1.8 * DB - k;
for (int t = 0; t < 2; ++t)
{
for (int x = 0; x < SCREEN_WIDTH + 2; ++x)
{
table[curT][t][SCREEN_HEIGHT - 1][x] = table[curT][t][1][x];
table[curT][t][0][x] = table[curT][t][SCREEN_HEIGHT - 2][x];
}
for (int y = 0; y < SCREEN_HEIGHT + 2; ++y)
{
table[curT][t][y][SCREEN_WIDTH - 1] = table[curT][t][y][1];
table[curT][t][y][0] = table[curT][t][y][SCREEN_WIDTH - 2];
}
table[curT][t][0][0] = table[curT][t][SCREEN_HEIGHT - 2][SCREEN_WIDTH - 2];
table[curT][t][0][SCREEN_WIDTH - 1] = table[curT][t][SCREEN_HEIGHT - 2][1];
table[curT][t][SCREEN_HEIGHT - 1][0] = table[curT][t][1][SCREEN_WIDTH - 2];
table[curT][t][SCREEN_HEIGHT - 1][SCREEN_WIDTH - 1] = table[curT][t][1][1];
}
Download source code
Download executable for Windows
Bonus: cool effect
int r = table[0][0][y+1][x+1] * 255;
int b = table[0][1][y+1][x+1] * 255;
int g = (table[0][0][y+1][x+1] - table[1][0][y+1][x+1]) * 650000;
g = ABS(g);
if (g > 255)
g = 255;
gfx.setPixel(x, y, Color(r, g, b));
Download source code
Download executable for Windows
Here is what we see when it runs: