summaryrefslogtreecommitdiff
path: root/mandelbrot_set.c
diff options
context:
space:
mode:
authorTanner Robison <[email protected]>2026-08-24 16:18:23 -0700
committerTanner Robison <[email protected]>2026-08-24 16:18:23 -0700
commitd84c42d5df0fb2dc8cd213f3f251e413345b22d6 (patch)
treefec56fc28e575ec17ea95d8e3d90657c8afa5afe /mandelbrot_set.c
parent18d3f45424e89dab81331bf2048a6a1e6a397f94 (diff)
functioning
Diffstat (limited to 'mandelbrot_set.c')
-rw-r--r--mandelbrot_set.c316
1 files changed, 316 insertions, 0 deletions
diff --git a/mandelbrot_set.c b/mandelbrot_set.c
new file mode 100644
index 0000000..4420372
--- /dev/null
+++ b/mandelbrot_set.c
@@ -0,0 +1,316 @@
+#include <complex.h>
+#include <math.h>
+#include <ncurses.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define INITIAL_WIDTH 3.2L
+#define INITIAL_ITERATIONS 100
+#define MAX_ITERATIONS 100000
+#define FRAME_DELAY 30000 // in microseconds
+#define ZOOM_RATE 0.965L
+
+static const char gradient[] = " .,:;irsXA253hMHGS#9B&@";
+
+struct viewer {
+ long double center_x;
+ long double center_y;
+ long double width;
+ long double target_x;
+ long double target_y;
+ long double zoom_rate;
+ size_t max_iterations;
+ bool paused;
+ bool show_status;
+};
+
+static int mandelbrot(long double cr, long double ci, size_t max_iterations);
+
+static char iteration_character(size_t iteration, size_t max_iterations);
+
+static void reset_viewer(struct viewer *viewer);
+
+static void initialize_viewer(struct viewer *viewer);
+
+static void update_zoom(struct viewer *viewer);
+
+static void draw_status(const struct viewer *viewer, int rows, int columns);
+
+static void draw_mandelbrot(const struct viewer *viewer);
+
+static void move_view(struct viewer *viewer, long double dx, long double dy);
+
+static bool handle_input(struct viewer *viewer);
+
+int main(void)
+{
+ struct viewer viewer;
+ bool running = true;
+
+ initialize_viewer(&viewer);
+
+ if (initscr() == NULL) {
+ fprintf(stderr, "Failed to init ncurses");
+ return -1;
+ }
+
+ cbreak();
+ noecho();
+ keypad(stdscr, true);
+ nodelay(stdscr, true);
+ curs_set(0);
+
+ while (running) {
+ draw_mandelbrot(&viewer);
+ running = handle_input(&viewer);
+ update_zoom(&viewer);
+ usleep(FRAME_DELAY);
+ }
+
+ endwin();
+ return 0;
+}
+
+static int mandelbrot(long double cr, long double ci, size_t max_iterations)
+{
+ long double zr = 0.0L;
+ long double zi = 0.0L;
+ size_t iteration;
+
+ for (iteration = 0; iteration < max_iterations; iteration++) {
+ long double zr2 = zr * zr;
+ long double zi2 = zi * zi;
+
+ if (zr2 + zi2 > 4.0) {
+ break;
+ }
+
+ zi = 2.0 * zr * zi + ci;
+ zr = zr2 - zi2 + cr;
+ }
+
+ return iteration;
+}
+
+static char iteration_character(size_t iteration, size_t max_iterations)
+{
+ size_t gradient_length = sizeof(gradient) - 2;
+ size_t index;
+
+ if (iteration >= max_iterations) {
+ return ' ';
+ }
+
+ // selecting the character
+ index = iteration * gradient_length / max_iterations;
+
+ if (index > gradient_length) {
+ index = gradient_length;
+ }
+
+ return gradient[index];
+}
+
+static void reset_viewer(struct viewer *viewer)
+{
+ viewer->center_x = 0.0L;
+ viewer->center_y = 0.0L;
+ viewer->width = INITIAL_WIDTH;
+ viewer->max_iterations = INITIAL_ITERATIONS;
+}
+
+static void initialize_viewer(struct viewer *viewer)
+{
+ reset_viewer(viewer);
+ viewer->target_x = -0.743643887037151L;
+ viewer->target_y = 0.131825904205330L;
+ viewer->zoom_rate = ZOOM_RATE;
+ viewer->paused = false;
+ viewer->show_status = true;
+}
+
+static void update_zoom(struct viewer *viewer)
+{
+ if (viewer->paused) {
+ return;
+ }
+
+ // scales movement magnitude based on zoom rate so camera is always in
+ // real center,
+ // i.e. the more zoomed in the smaller movements become
+ long double movement = 1.0L - viewer->zoom_rate;
+
+ viewer->center_x += (viewer->target_x - viewer->center_x) * movement;
+ viewer->center_y += (viewer->target_y - viewer->center_y) * movement;
+ viewer->width *= viewer->zoom_rate;
+
+ // increases number of iterations as zoom increases to see finer detail
+ viewer->max_iterations =
+ INITIAL_ITERATIONS +
+ (int)(40.0L * logl(INITIAL_WIDTH / viewer->width));
+
+ if (viewer->max_iterations > MAX_ITERATIONS)
+ viewer->max_iterations = MAX_ITERATIONS;
+
+ if (viewer->width < 1.0e-16L)
+ reset_viewer(viewer);
+}
+
+static void draw_status(const struct viewer *viewer, int rows, int columns)
+{
+
+ if (!viewer->show_status || rows < 2) {
+ return;
+ }
+
+ char status[256];
+ snprintf(status, sizeof(status),
+ "center %.18Lf %+.18Lfi width %.3Le iter %zu %s",
+ viewer->center_x, viewer->center_y, viewer->width,
+ viewer->max_iterations,
+ viewer->paused ? "PAUSED" : "AUTO ZOOM");
+
+ attron(A_REVERSE);
+ mvaddnstr(0, 0, status, columns);
+
+ // fill rest of row with spaces
+ for (int column = (int)strlen(status); column < columns; column++) {
+ mvaddch(0, column, ' ');
+ }
+ attroff(A_REVERSE);
+}
+
+static void draw_mandelbrot(const struct viewer *viewer)
+{
+ size_t rows;
+ size_t columns;
+
+ getmaxyx(stdscr, rows, columns);
+
+ // if status bar is on -> start row is 1 not 0
+ int start_row = viewer->show_status ? 1 : 0;
+
+ if (rows - start_row <= 0 || columns <= 0) {
+ return;
+ }
+
+ long double height = viewer->width * (long double)(rows - start_row) /
+ (long double)columns * 2.0L;
+
+ erase();
+
+ for (int screen_y = start_row; screen_y < rows; screen_y++) {
+ long double normalized_y = (long double)(screen_y - start_row) /
+ (long double)(rows - start_row - 1);
+
+ long double ci =
+ viewer->center_y + (normalized_y - 0.5L) * height;
+
+ for (int screen_x = 0; screen_x < columns; screen_x++) {
+ long double normalized_x =
+ (long double)screen_x / (long double)(columns - 1);
+
+ long double cr = viewer->center_x +
+ (normalized_x - 0.5L) * viewer->width;
+
+ int iteration =
+ mandelbrot(cr, ci, viewer->max_iterations);
+
+ char pixel = iteration_character(
+ iteration, viewer->max_iterations);
+
+ mvaddch(screen_y, screen_x, pixel);
+ }
+ }
+
+ draw_status(viewer, rows, columns);
+ refresh();
+}
+
+static void move_view(struct viewer *viewer, long double dx, long double dy)
+{
+ long double height = viewer->width * 0.5L;
+
+ viewer->center_x += viewer->width * dx;
+ viewer->center_y += height * dy;
+ viewer->target_x += viewer->width * dx;
+ viewer->target_y += height * dy;
+}
+
+static bool handle_input(struct viewer *viewer)
+{
+ int key = getch();
+
+ switch (key) {
+ case 'q':
+ case 'Q':
+ return false;
+
+ case ' ':
+ case 'p':
+ case 'P':
+ viewer->paused = !viewer->paused;
+ break;
+
+ case 'r':
+ case 'R':
+ reset_viewer(viewer);
+ break;
+
+ case 's':
+ case 'S':
+ viewer->show_status = !viewer->show_status;
+ break;
+
+ case '+':
+ case '=':
+ viewer->width *= 0.8L;
+ break;
+
+ case '-':
+ case '_':
+ viewer->width *= 1.25L;
+ break;
+
+ case KEY_LEFT:
+ case 'h':
+ move_view(viewer, -0.1L, 0.0L);
+ break;
+
+ case KEY_RIGHT:
+ case 'l':
+ move_view(viewer, 0.1L, 0.0L);
+ break;
+
+ case KEY_UP:
+ case 'k':
+ move_view(viewer, 0.0L, -0.1L);
+ break;
+
+ case KEY_DOWN:
+ case 'j':
+ move_view(viewer, 0.0L, 0.1L);
+ break;
+
+ case '[':
+ viewer->zoom_rate -= 0.005L;
+ if (viewer->zoom_rate < 0.8L)
+ viewer->zoom_rate = 0.8L;
+ break;
+
+ case ']':
+ viewer->zoom_rate += 0.005L;
+ if (viewer->zoom_rate > 0.999L)
+ viewer->zoom_rate = 0.999L;
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}