Created
February 13, 2022 09:48
-
-
Save eliaxelang007/56605b6d0338d77d79dca658efe55b38 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <opencv2/opencv.hpp> | |
#include <opencv2/highgui.hpp> | |
using namespace cv; | |
const int rows = 216; | |
const int columns = 384; | |
const int chemicals = 2; | |
bool is_drawing = false; | |
int brush_size = 2; | |
// This loops a value. If the value passes [max] then it returns the min and vice versa. | |
int loop(int value, int min, int max) | |
{ | |
if (value == 0) | |
{ | |
return min; | |
} | |
int looped_value = value % (max - min); | |
if (looped_value == 0) | |
{ | |
return max * (value / abs(value)); | |
} | |
return looped_value + min; | |
} | |
// This constrains a value between [min] and [max]. | |
float constrain(float value, float min, float max) | |
{ | |
if (value > max) | |
{ | |
return max; | |
} | |
if (value < min) | |
{ | |
return min; | |
} | |
return value; | |
} | |
// This function takes in a cell in a grid and returns a value based on the status of the cells around it. | |
float laplace(bool is_b_or_a, int x, int y, float cell_grid[rows][columns][chemicals]) | |
{ | |
float corner_weight = 0.05f; | |
float cross_weight = 0.2f; | |
float laplace_value = 0.0f; | |
int b_or_a = (int)is_b_or_a; | |
// Since I'm checking for the cells around a cell, what would happend if the cell was at 0, 0? | |
// Since the x is zero, x - 1 would be negative one but negative one doesn't exist in an array. | |
// My solution to this is to loop x. If the width of the array was 100, -1 would be 99(98 because array logic); | |
int looped_y = loop(y, 0, rows); | |
int looped_x = loop(x, 0, columns); | |
int looped_upper_y = loop(y + 1, 0, (rows - 1)); | |
int looped_lower_y = loop(y - 1, 0, (rows - 1)); | |
int looped_upper_x = loop(x + 1, 0, (columns - 1)); | |
int looped_lower_x = loop(x - 1, 0, (columns - 1)); | |
// Calculate a value from the status of the cells around a cell. | |
laplace_value += cell_grid[looped_y][looped_x][b_or_a] * -1; | |
laplace_value += cell_grid[looped_upper_y][x][b_or_a] * cross_weight; | |
laplace_value += cell_grid[looped_lower_y][x][b_or_a] * cross_weight; | |
laplace_value += cell_grid[y][looped_upper_x][b_or_a] * cross_weight; | |
laplace_value += cell_grid[y][looped_lower_x][b_or_a] * cross_weight; | |
laplace_value += cell_grid[looped_upper_y][looped_upper_x][b_or_a] * corner_weight; | |
laplace_value += cell_grid[looped_lower_y][looped_lower_x][b_or_a] * corner_weight; | |
laplace_value += cell_grid[looped_upper_y][looped_lower_x][b_or_a] * corner_weight; | |
laplace_value += cell_grid[looped_lower_y][looped_upper_x][b_or_a] * corner_weight; | |
return laplace_value; | |
} | |
// The cell gird has 2 chemicals in it. This adds chemical A to the whole grid. | |
void initializeCellGrid(float cell_grid[rows][columns][chemicals]) | |
{ | |
for (int j = 0; j < rows; ++j) | |
{ | |
for (int i = 0; i < columns; ++i) | |
{ | |
// Set chemical a ammount to 1 | |
cell_grid[j][i][0] = 1; | |
cell_grid[j][i][1] = 0; | |
} | |
} | |
} | |
// This adds chemical B to the grid in a circle to start chemical A and B reactions. | |
void addChemicalB(float cell_grid[rows][columns][chemicals], float ammount_to_add, int x_position, int y_position, int radius) | |
{ | |
for (int y = 0; y < rows; ++y) | |
{ | |
for (int x = 0; x < columns; ++x) | |
{ | |
float delta_x = (float)x - x_position; | |
float delta_y = (float)y - y_position; | |
float distanceSquared = delta_x * delta_x + delta_y * delta_y; | |
if (distanceSquared <= (radius * radius)) | |
{ | |
cell_grid[y][x][1] += ammount_to_add; | |
} | |
} | |
} | |
} | |
// I guess this needs no explaining | |
void addChemicalBOnClick(int event, int x, int y, int flags, void* cell_grid) | |
{ | |
int chemical_b_radius = (int)(((brush_size*0.01f) * min(columns, rows))*0.5f); | |
float chemical_b_to_add = 0.5f; | |
switch (event) | |
{ | |
case EVENT_LBUTTONDOWN: | |
is_drawing = true; | |
addChemicalB((float(*) [columns][chemicals])cell_grid, chemical_b_to_add, x, y, chemical_b_radius); | |
break; | |
case EVENT_MOUSEMOVE: | |
if (is_drawing == true) | |
{ | |
addChemicalB((float(*) [columns][chemicals])cell_grid, chemical_b_to_add, x, y, chemical_b_radius); | |
} | |
break; | |
case EVENT_LBUTTONUP: | |
addChemicalB((float(*) [columns][chemicals])cell_grid, chemical_b_to_add, x, y, chemical_b_radius); | |
is_drawing = false; | |
break; | |
} | |
} | |
// Since the cell grid itself isn't formatted as an image, I have to the convert the 0 to 1 values to 0 to 255 values to show it as an image. | |
void updateCellGridImage(Mat &cell_grid_image, float cell_grid[rows][columns][chemicals]) | |
{ | |
for (int j = 0; j < rows; ++j) | |
{ | |
for (int i = 0; i < columns; ++i) | |
{ | |
cell_grid_image.at<uint8_t>(j, i) = (uint8_t)(fdimf(cell_grid[j][i][0], cell_grid[j][i][1]) * 255); | |
} | |
} | |
} | |
// I guess this just updates the cell grid | |
void updateCellGrid(float cell_grid[rows][columns][chemicals], float next_cell_grid[rows][columns][chemicals], float a_diffusion_rate, float b_diffusion_rate, float feed_rate, float kill_rate) | |
{ | |
// This calculates the new cell grid values, | |
for (int j = 0; j < rows; ++j) | |
{ | |
for (int i = 0; i < columns; ++i) | |
{ | |
float chemical_a_ammount = cell_grid[j][i][0]; | |
float chemical_b_ammount = cell_grid[j][i][1]; | |
// This is where the magic happens. | |
// These equations baiscally change the values of chemical A and chemical B | |
next_cell_grid[j][i][0] = chemical_a_ammount + a_diffusion_rate * laplace(false, i, j, cell_grid) - chemical_a_ammount * chemical_b_ammount * chemical_b_ammount - chemical_a_ammount * feed_rate + feed_rate; | |
next_cell_grid[j][i][1] = chemical_b_ammount + b_diffusion_rate * laplace(true, i, j, cell_grid) + chemical_a_ammount * chemical_b_ammount * chemical_b_ammount - chemical_b_ammount * feed_rate - chemical_b_ammount * kill_rate; | |
} | |
} | |
// and this sets the old grid's values to the new grid's values | |
for (int j = 0; j < rows; ++j) | |
{ | |
for (int i = 0; i < columns; ++i) | |
{ | |
cell_grid[j][i][0] = constrain(next_cell_grid[j][i][0], 0.0f, 1.0f); | |
cell_grid[j][i][1] = constrain(next_cell_grid[j][i][1], 0.0f, 1.0f); | |
} | |
} | |
} | |
int main() | |
{ | |
float cell_grid[rows][columns][chemicals] = {0}; | |
float next_cell_grid[rows][columns][chemicals] = {0}; | |
initializeCellGrid(cell_grid); | |
initializeCellGrid(next_cell_grid); | |
addChemicalB(cell_grid, 1.0f, columns*0.5f, rows*0.5f, 5); | |
addChemicalB(next_cell_grid, 1.0f, columns*0.5f, rows*0.5f, 5); | |
Mat cell_grid_image = Mat(rows, columns, CV_8U); | |
std::string main_window_name = "Reaction Diffusion Simulation"; | |
std::string slider_window_name = "Sliders"; | |
namedWindow(main_window_name, WINDOW_NORMAL); | |
namedWindow(slider_window_name, WINDOW_NORMAL); | |
setMouseCallback(main_window_name, addChemicalBOnClick, (void*)cell_grid); | |
// By default, these values are set to simulate coral growth. | |
int feed_rate = 55; | |
int kill_rate = 62; | |
int window_size = 1; | |
// Trackbar = Slider | |
createTrackbar("Feed Rate", slider_window_name, &feed_rate, 100); | |
createTrackbar("Kill Rate", slider_window_name, &kill_rate, 73); | |
createTrackbar("Brush Size", slider_window_name, &brush_size, 100); | |
createTrackbar("Win Size", slider_window_name, &window_size, 100); | |
while (waitKey(1) < 0) | |
{ | |
updateCellGrid(cell_grid, next_cell_grid, 1.0f, 0.5f, (float)feed_rate*0.001f, (float)kill_rate*0.001f); | |
updateCellGridImage(cell_grid_image, cell_grid); | |
imshow(main_window_name, cell_grid_image); | |
resizeWindow(main_window_name, (columns + (1920-columns) * (window_size*0.01f)), ((rows) + (1080-rows) * (window_size*0.01f))); | |
} | |
destroyAllWindows(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment