diff --git a/organism/Makefile b/organism/Makefile new file mode 100644 index 0000000..c5e3ab2 --- /dev/null +++ b/organism/Makefile @@ -0,0 +1,21 @@ +# Uncomment lines below if you have problems with $PATH +#SHELL := /bin/bash +#PATH := /usr/local/bin:$(PATH) + +all: + pio.exe -f -c vim run + +upload: + pio.exe -f -c vim run --target upload + +clean: + pio.exe -f -c vim run --target clean + +program: + pio.exe -f -c vim run --target program + +uploadfs: + pio.exe -f -c vim run --target uploadfs + +update: + pio.exe -f -c vim update diff --git a/organism/platformio.ini b/organism/platformio.ini new file mode 100644 index 0000000..0e62411 --- /dev/null +++ b/organism/platformio.ini @@ -0,0 +1,22 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:adafruit_matrixportal_esp32s3] +platform = espressif32 +board = adafruit_matrixportal_esp32s3 +framework = arduino +monitor_speed = 115200 + +lib_deps = + adafruit/Adafruit GFX Library + adafruit/Adafruit BusIO + Wire + fastled/FastLED + https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA.git diff --git a/organism/src/blur.cpp b/organism/src/blur.cpp new file mode 100644 index 0000000..e6f5f56 --- /dev/null +++ b/organism/src/blur.cpp @@ -0,0 +1,105 @@ +#include +#include "constants.h" +#include "config.h" +#include "util.h" + +void setupGaussianKernel(float* kernel, int width, float sigma) { + float sum = 0.0; + for(int i = 0; i < width; i++) { + //kernel[width+i] = exp( -(i*i) / (2 * sigma * sigma)) / (PI * 2 * sigma * sigma); + kernel[i] = exp(-0.5f * (i * i) / (sigma * sigma)); + sum += kernel[width]; + } + for(int i; i < width; i++) { + kernel[i] = sum/width; + } +} + +//float first_pass[PANEL_WIDTH][PANEL_HEIGHT]; +void gaussian_blur(float *field[PANEL_WIDTH][PANEL_HEIGHT]) { + float kernel[GAUSS_WIDTH]; + float first_pass[PANEL_WIDTH][PANEL_HEIGHT]; + memset(first_pass, 0.0, sizeof first_pass); + setupGaussianKernel(kernel, GAUSS_WIDTH, GAUSS_SIGMA); + // horizontal pass + for (int x = 0; x < PANEL_WIDTH; x++) { + for(int y = 0; y < PANEL_HEIGHT; y++) { + float sum = *field[x][y] * DECAY_FACTOR * kernel[0]; //0.0; + int additions = 1; + for (int x_offset = 1; x_offset < GAUSS_WIDTH; x_offset++) { + if (is_in_bounds(x+x_offset, y)) { + sum += *field[x + x_offset][y] * kernel[x_offset]; + additions++; + } + if (is_in_bounds(x-x_offset, y)) { + sum += *field[x - x_offset][y] * kernel[x_offset]; + additions++; + } + } + first_pass[x][y] = sum/GAUSS_WIDTH; + } + } + // vertical pass + for (int x = 0; x < PANEL_WIDTH; x++) { + for(int y = 0; y < PANEL_HEIGHT; y++) { + float sum = first_pass[x][y] * kernel[0]; //0.0; + int additions = 1; + for (int y_offset = 1; y_offset < GAUSS_WIDTH; y_offset++) { + if (is_in_bounds(x, y + y_offset)) { + sum += first_pass[x][y + y_offset] * kernel[y_offset]; + additions++; + } + if (is_in_bounds(x, y - y_offset)) { + sum += first_pass[x][y - y_offset] * kernel[y_offset]; + additions++; + } + } + *field[x][y] = sum/GAUSS_WIDTH; + } + } +} + +void box_blur(float field[PANEL_WIDTH][PANEL_HEIGHT]) { + auto first_pass = new float[PANEL_WIDTH][PANEL_HEIGHT](); + // horizontal pass + for (int x = 0; x < PANEL_WIDTH; x++) { + for(int y = 0; y < PANEL_HEIGHT; y++) { + float sum; + sum = field[x][y] * DECAY_FACTOR; //0.0; + int additions = 1; + for (int x_offset = 1; x_offset < BLUR_KERNEL_SIZE; x_offset++) { + if (is_in_bounds(x+x_offset, y)) { + sum += field[x + x_offset][y]; + additions++; + } + if (is_in_bounds(x-x_offset, y)) { + sum += field[x - x_offset][y]; + additions++; + } + } + first_pass[x][y] = (sum/additions); + } + } + // vertical pass + for (int x = 0; x < PANEL_WIDTH; x++) { + for(int y = 0; y < PANEL_HEIGHT; y++) { + float sum = first_pass[x][y]; //0.0; + int additions = 1; + for (int y_offset = 1; y_offset < BLUR_KERNEL_SIZE; y_offset++) { + if (is_in_bounds(x, y + y_offset)) { + sum += first_pass[x][y + y_offset]; + additions++; + } + if (is_in_bounds(x, y - y_offset)) { + sum += first_pass[x][y - y_offset]; + additions++; + } + } + + float result = (sum/additions); // * DECAY_FACTOR; + //attractant[x][y] = (((8.0*attractant[x][y]) + result) / 9.0); + field[x][y] = result; + } + } + delete[] first_pass; +} diff --git a/organism/src/blur.h b/organism/src/blur.h new file mode 100644 index 0000000..3ca9090 --- /dev/null +++ b/organism/src/blur.h @@ -0,0 +1,3 @@ +#include "constants.h" +void gaussian_blur(float *field[PANEL_WIDTH][PANEL_HEIGHT]); +void box_blur(float field[PANEL_WIDTH][PANEL_HEIGHT]); diff --git a/organism/src/config.h b/organism/src/config.h new file mode 100644 index 0000000..f9ad7f1 --- /dev/null +++ b/organism/src/config.h @@ -0,0 +1,18 @@ +// gaussian blur +#define GAUSS_WIDTH 1 +#define GAUSS_SIGMA 1.0 +#define DECAY_FACTOR 0.9 +// box blur +#define BLUR_KERNEL_SIZE 1 +// agent params +#define NUM_AGENTS_MIN 64 +#define NUM_AGENTS_MAX 301 +#define AGENT_DROP_AMOUNT 10 +#define AGENT_SENSOR_DISTANCE_MIN 2.0 +#define AGENT_SENSOR_DISTANCE_MAX 3.5 +// general params +#define NUM_ITERATIONS 500 +#define SPAWN_MIN_X 2 +#define SPAWN_MAX_X 63 +#define SPAWN_MIN_Y 2 +#define SPAWN_MAX_Y 31 diff --git a/organism/src/constants.h b/organism/src/constants.h new file mode 100644 index 0000000..0561782 --- /dev/null +++ b/organism/src/constants.h @@ -0,0 +1,26 @@ +#define R1 42 +#define G1 40 +#define BL1 41 +#define R2 38 +#define G2 37 +#define BL2 39 +#define CH_A 45 +#define CH_B 36 +#define CH_C 48 +#define CH_D 35 +#define CH_E 21 +#define CLK 2 +#define LAT 47 +#define OE 14 + +#define PIN_E 21 +#define PANEL_WIDTH 64 +#define PANEL_HEIGHT 32 // Panel height of 64 will required PIN_E to be defined. + +#define PANELS_NUMBER 1 + +#define PANE_WIDTH PANEL_WIDTH * PANELS_NUMBER +#define PANE_HEIGHT PANEL_HEIGHT +#define NUM_LEDS PANE_WIDTH*PANE_HEIGHT + +#define ONBOARD_LED 13 diff --git a/organism/src/main.cpp b/organism/src/main.cpp new file mode 100644 index 0000000..01bc40e --- /dev/null +++ b/organism/src/main.cpp @@ -0,0 +1,177 @@ +#include +#include "xtensa/core-macros.h" +#include + +#include "main.h" +#include "constants.h" +#include "config.h" +#include "overlay.h" +#include "util.h" + +#include "visual.h" +#include "blur.h" + +#include + + +MatrixPanel_I2S_DMA *matrix = nullptr; + + +struct SlimeAgent { + int x_position; + int y_position; + float heading; +}; + + +int NUM_AGENTS; +int AGENT_MOVE_DISTANCE; +float AGENT_SENSOR_DISTANCE; +float AGENT_MOVE_ANGLE; +float AGENT_SENSOR_ANGLE; + +float attractant[PANEL_WIDTH][PANEL_HEIGHT] = {}; +int iterations = 0; +std::vector agents; + +void init_attractant() { + memset(attractant, 0.0, sizeof attractant); + // draw a circle out of attractant + //for (int p = 0; p < PANEL_WIDTH; p++) { + // float angle = ((PI * 2) / PANEL_WIDTH) * p; + // int x = PANEL_WIDTH/2 + round(10 * sin(angle)); + // int y = PANEL_WIDTH/2 + round(10 * cos(angle)); + // attractant[x][y] = 15.0; + //} + + // draw a bitmap + //for(int x = 0; x < PANEL_WIDTH; x++) { + // for(int y = 0; y < PANEL_HEIGHT; y++) { + // int pixel_num = (y * PANEL_WIDTH) + x; + // if(overlay[ pixel_num / 8 ] & (1 << (7 - (pixel_num % 8)) ) ){ + // attractant[x][y] = 255.0; + // } + // } + //} +} + +void init_agents() { + AGENT_MOVE_DISTANCE = 1; + AGENT_MOVE_ANGLE = 0.175;//random(0, 334) / 1000.0; + //if (random(0, 2) > 0) { + // AGENT_SENSOR_ANGLE = 0.175; + //} else { + AGENT_SENSOR_ANGLE = 0.0875; + //} + AGENT_SENSOR_DISTANCE = random(AGENT_SENSOR_DISTANCE_MIN * 1000.0, AGENT_SENSOR_DISTANCE_MAX * 1000.0) / 1000.0; + NUM_AGENTS = random(NUM_AGENTS_MIN, NUM_AGENTS_MAX); + + agents.clear(); + for(int a = 0; a < NUM_AGENTS; a++) { + float angle = ((PI * 2) / NUM_AGENTS) * a; + //agents[a].x_position = 31 + round(10*sin(angle)); + //agents[a].y_position = 15 + round(10*cos(angle)); + // agents[a].heading = ((PI * 2) / NUM_AGENTS) * ); + agents.push_back({ + random(SPAWN_MIN_X, SPAWN_MAX_X), + random(SPAWN_MIN_Y, SPAWN_MAX_Y), + (random(0, 100) / 100.0) * (PI*2) + }); + } +} + + + +void setup(){ + Serial.begin(BAUD_RATE); + //while (!Serial) { + // ; // wait for serial port to connect. Needed for native USB + //} + pinMode(ONBOARD_LED, OUTPUT); + + // redefine pins if required + HUB75_I2S_CFG::i2s_pins _pins={R1, G1, BL1, R2, G2, BL2, CH_A, CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; + HUB75_I2S_CFG mxconfig(PANEL_WIDTH, PANEL_HEIGHT, PANELS_NUMBER, _pins); + + mxconfig.gpio.e = PIN_E; + mxconfig.driver = HUB75_I2S_CFG::FM6126A; // for panels using FM6126A chips + + mxconfig.latch_blanking = 4; + mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; + mxconfig.clkphase = false; + + mxconfig.double_buff = true; + + matrix = new MatrixPanel_I2S_DMA(mxconfig); + matrix->begin(); + matrix->setBrightness8(64); + matrix->fillScreenRGB888(0, 0, 0); + + init_agents(); + init_attractant(); +} + +void loop() { + matrix ->flipDMABuffer(); + delay(10); + matrix->fillScreenRGB888(15, 0, 10); + + // draw background and organism + draw_bg(matrix); + draw_attractant(matrix, attractant); + + for(int a = 0; a < NUM_AGENTS; a++) { + //matrix->drawPixel(agents[a].x_position, agents[a].y_position, matrix->color565(0, 0, attractant[agents[a].x_position][agents[a].y_position])); + // motor step: check if agent can move forward + // if yes, do so, if not, turn randomly + float target_x = agents[a].x_position + cos(agents[a].heading) * AGENT_MOVE_DISTANCE; + float target_y = agents[a].y_position + sin(agents[a].heading) * AGENT_MOVE_DISTANCE; + int move_x = round(target_x); + int move_y = round(target_y); + if (is_in_bounds(move_x, move_y)) { + agents[a].x_position = move_x; + agents[a].y_position = move_y; + if (attractant[move_x][move_y] < 255.0 - AGENT_DROP_AMOUNT) { + attractant[move_x][move_y] += AGENT_DROP_AMOUNT; + } + } else { + agents[a].heading = (random(0, 100)/100.0) * (PI*2); + } + } + // sample step - check the 3 positions + for(int a = 0; a < NUM_AGENTS; a++) { + //matrix->drawPixel(agents[a].x_position, agents[a].y_position, matrix->color565(0, 0, attractant[agents[a].x_position][agents[a].y_position])); + int front_x = round(agents[a].x_position + cos(agents[a].heading) * AGENT_SENSOR_DISTANCE); + int front_y = round(agents[a].y_position + sin(agents[a].heading) * AGENT_SENSOR_DISTANCE); + int left_x = round(agents[a].x_position + cos(agents[a].heading - (AGENT_SENSOR_ANGLE * (PI * 2))) * AGENT_SENSOR_DISTANCE); + int left_y = round(agents[a].y_position + sin(agents[a].heading - (AGENT_SENSOR_ANGLE * (PI * 2))) * AGENT_SENSOR_DISTANCE); + int right_x = round(agents[a].x_position + cos(agents[a].heading + (AGENT_SENSOR_ANGLE * (PI * 2))) * AGENT_SENSOR_DISTANCE); + int right_y = round(agents[a].y_position + sin(agents[a].heading + (AGENT_SENSOR_ANGLE * (PI * 2))) * AGENT_SENSOR_DISTANCE); + float front_value = attractant[front_x][front_y]; + float left_value = attractant[left_x][left_y]; + float right_value = attractant[right_x][right_y]; + if (front_value > right_value and front_value > left_value) { + ; + } else if (front_value < right_value and front_value < left_value) { + agents[a].heading += (random(0, 2) * 2 - 1) * (AGENT_MOVE_ANGLE / 100.0) * (PI*2) ; + } else if (left_value > right_value) { + agents[a].heading -= AGENT_MOVE_ANGLE * (PI * 2); + } else { + agents[a].heading += AGENT_MOVE_ANGLE * (PI * 2); + } + } + iterations++; + if (iterations % 2 == 0) { //64 && iterations % 64 == 0) { // (( == 0) { + box_blur(attractant); + //gaussian_blur(); + ; + } + if (iterations >= NUM_ITERATIONS) { + //cleanup(); + if (cleanup(matrix, &iterations)) { + init_attractant(); + init_agents(); + } + } +} + diff --git a/organism/src/main.h b/organism/src/main.h new file mode 100644 index 0000000..e4e784a --- /dev/null +++ b/organism/src/main.h @@ -0,0 +1,10 @@ +#include + +#define BAUD_RATE 115200 // serial debug port baud rate + +void buffclear(CRGB *buf); +uint16_t XY16( uint16_t x, uint16_t y); +void mxfill(CRGB *leds); +uint16_t colorWheel(uint8_t pos); +void drawText(int colorWheelOffset); + diff --git a/organism/src/overlay.h b/organism/src/overlay.h new file mode 100644 index 0000000..f87ab77 --- /dev/null +++ b/organism/src/overlay.h @@ -0,0 +1,19 @@ +const unsigned char overlay [] PROGMEM = { + // 'bw2, 64x32px + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, + 0x01, 0xf8, 0x00, 0x00, 0x00, 0x1c, 0x03, 0x80, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x1c, 0x03, 0x80, + 0x01, 0xff, 0x00, 0x00, 0x00, 0x1c, 0x03, 0x80, 0x01, 0xc7, 0x80, 0x00, 0x00, 0x1c, 0x00, 0x00, + 0x01, 0xc3, 0x80, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x01, 0xc3, 0x8f, 0xe1, 0xbe, 0x1c, 0x7b, 0x80, + 0x01, 0xc3, 0x8f, 0xe1, 0xff, 0x1c, 0x73, 0x80, 0x01, 0xc3, 0x88, 0x71, 0xe7, 0x1c, 0xe3, 0x80, + 0x01, 0xc3, 0x80, 0x71, 0xc3, 0x9d, 0xe3, 0x80, 0x01, 0xff, 0x00, 0x71, 0xc3, 0x9d, 0xc3, 0x80, + 0x01, 0xff, 0x03, 0xf1, 0xc3, 0x9f, 0x83, 0x80, 0x01, 0xfc, 0x0f, 0xf1, 0xc3, 0x9f, 0xc3, 0x80, + 0x01, 0xc0, 0x1f, 0x71, 0xc3, 0x9f, 0xc3, 0x80, 0x01, 0xc0, 0x3c, 0x71, 0xc3, 0x9d, 0xe3, 0x80, + 0x01, 0xc0, 0x38, 0x71, 0xc3, 0x9c, 0xe3, 0x80, 0x01, 0xc0, 0x38, 0x71, 0xc3, 0x9c, 0xf3, 0x80, + 0x01, 0xc0, 0x1c, 0xf1, 0xc3, 0x9c, 0x73, 0x80, 0x01, 0xc0, 0x1f, 0xf1, 0xc3, 0x9c, 0x7b, 0x80, + 0x01, 0xc0, 0x0f, 0xb1, 0x83, 0x9c, 0x3b, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; diff --git a/organism/src/util.cpp b/organism/src/util.cpp new file mode 100644 index 0000000..e445ef5 --- /dev/null +++ b/organism/src/util.cpp @@ -0,0 +1,5 @@ +#include "constants.h" + +bool is_in_bounds(int x, int y) { + return (x < PANEL_WIDTH) and (y < PANEL_HEIGHT) and (x >= 0) and (y >= 0); +} diff --git a/organism/src/util.h b/organism/src/util.h new file mode 100644 index 0000000..3289174 --- /dev/null +++ b/organism/src/util.h @@ -0,0 +1,2 @@ +bool is_in_bounds(int x, int y); + diff --git a/organism/src/visual.cpp b/organism/src/visual.cpp new file mode 100644 index 0000000..4f50b49 --- /dev/null +++ b/organism/src/visual.cpp @@ -0,0 +1,54 @@ +#include "constants.h" +#include "visual.h" + +void draw_bg(MatrixPanel_I2S_DMA *matrix) { + int h_lines = round(PANEL_HEIGHT / 7); + int v_lines = round(PANEL_WIDTH / 7); + for(int v = 0; v <= v_lines; v++) { + matrix->drawFastVLine(v*7+2, 0, PANEL_HEIGHT, matrix->color565(41, 17, 76)); + } + for(int h = 0; h <= h_lines; h++) { + matrix->drawFastHLine(0, 7*h + 1, PANEL_WIDTH, matrix->color565(41, 17, 76)); + } +} + +void draw_attractant(MatrixPanel_I2S_DMA *matrix, float attractant[PANEL_WIDTH][PANEL_HEIGHT]) { + for(int x = 0; x 15.0) { + matrix->drawPixel(x, y, matrix->color565( + int((255.0 / 255.0) * round(attractant[x][y])), + int((80.0 / 255.0) * round(attractant[x][y])), + int((83.0 / 255.0) * round(attractant[x][y])) + ) + ); + } + } + } +} + +int cleanup_x = 0; +bool cleanup(MatrixPanel_I2S_DMA *matrix, int* iterations) { + int progress = *iterations - NUM_ITERATIONS; + matrix->drawFastVLine(cleanup_x, 0, PANEL_HEIGHT, matrix->color565(254, 242, 255)); + if (cleanup_x > 0) { + matrix->fillRect(0, 0, cleanup_x, PANEL_HEIGHT, matrix->color565(16, 0, 16)); + } + int h_lines = round(PANEL_HEIGHT / 7); + int v_lines = round(cleanup_x / 7); + for(int v = 0; v < cleanup_x; v++) { + if (v%7 == 2) { + matrix->drawFastVLine(v, 0, PANEL_HEIGHT, matrix->color565(41, 17, 76)); + } + } + for(int h = 0; h <= h_lines; h++) { + matrix->drawFastHLine(0, 7*h + 1, cleanup_x-1, matrix->color565(41, 17, 76)); + } + cleanup_x++; + if (cleanup_x > PANEL_WIDTH) { + *iterations = 0; + cleanup_x = 0; + return true; + } + return false; +} diff --git a/organism/src/visual.h b/organism/src/visual.h new file mode 100644 index 0000000..04659d1 --- /dev/null +++ b/organism/src/visual.h @@ -0,0 +1,6 @@ +#include +#include "constants.h" +#include "config.h" +void draw_bg(MatrixPanel_I2S_DMA *matrix); +void draw_attractant(MatrixPanel_I2S_DMA *matrix, float attractant[PANEL_WIDTH][PANEL_HEIGHT]); +bool cleanup(MatrixPanel_I2S_DMA *matrix, int* iterations);