#include "mandelbrot.h" #include #include #include #include #include #include #include #include #include #include #include #include extern int points[WINDOW_WIDTH * WINDOW_HEIGHT]; state_t* init() { state_t* state = malloc(sizeof(state_t)); if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) { fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError()); return NULL; } SDL_Window* window = SDL_CreateWindow(WINDOW_TITLE, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_HIDDEN); if (window == NULL) { fprintf(stderr, "CreateWindow failed: %s\n", SDL_GetError()); SDL_Quit(); free(state); return NULL; } SDL_Surface* surface = SDL_GetWindowSurface(window); if (!surface) { fprintf(stderr, "GetWindowSurface failed: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_Quit(); free(state); return NULL; } SDL_Renderer* renderer = SDL_CreateSoftwareRenderer(surface); if (renderer == NULL) { fprintf(stderr, "CreateSoftwareRenderer failed: %s\n", SDL_GetError()); SDL_DestroyWindow(window); SDL_DestroySurface(surface); SDL_Quit(); free(state); return NULL; } state->window = window; state->surface = surface; state->renderer = renderer; return state; } typedef struct { position_t* pos; int start_row; int end_row; int max_iter; } thread_data_t; void* calculate_points_thread(void* arg) { thread_data_t* data = (thread_data_t*)arg; position_t* pos = data->pos; int max_iter = data->max_iter; for (int py = data->start_row; py < data->end_row; py++) { for (int px = 0; px < WINDOW_WIDTH; px++) { double cr = pos->x_min + (pos->x_max - pos->x_min) * px / (WINDOW_WIDTH - 1); double ci = pos->y_min + (pos->y_max - pos->y_min) * py / (WINDOW_HEIGHT - 1); points[py * WINDOW_WIDTH + px] = mandelbrot_iterations(cr, ci, max_iter); } } return NULL; } void calculate_points_pthread(position_t* pos) { int max_iter = 100; int num_threads = 4; pthread_t threads[num_threads]; thread_data_t thread_data[num_threads]; int rows_per_thread = WINDOW_HEIGHT / num_threads; int remaining_rows = WINDOW_HEIGHT % num_threads; for (int t = 0; t < num_threads; t++) { thread_data[t].pos = pos; thread_data[t].start_row = t * rows_per_thread; thread_data[t].end_row = (t + 1) * rows_per_thread; thread_data[t].max_iter = max_iter; if (t == num_threads - 1) { thread_data[t].end_row += remaining_rows; } pthread_create(&threads[t], NULL, calculate_points_thread, &thread_data[t]); } for (int t = 0; t < num_threads; t++) { pthread_join(threads[t], NULL); } } int main(void) { state_t* state = init(); if (state == NULL) { return -1; } SDL_Event e; bool running = true; SDL_ShowWindow(state->window); position_t position; position.x_min = -2.0; position.x_max = 1.0; position.y_min = -1.2; position.y_max = 1.2; bool pos_updated = true; char debug_text[256] = ""; bool is_dragging = false; float drag_start_x, drag_start_y; double drag_start_x_min, drag_start_x_max; double drag_start_y_min, drag_start_y_max; while (running) { while (SDL_PollEvent(&e)) { if (e.type == SDL_EVENT_QUIT) { running = false; } if (e.type == SDL_EVENT_MOUSE_WHEEL) { float mx, my; SDL_GetMouseState(&mx, &my); double cr = position.x_min + (position.x_max - position.x_min) * mx / (WINDOW_WIDTH - 1); double ci = position.y_min + (position.y_max - position.y_min) * my / (WINDOW_HEIGHT - 1); double range_y = position.y_max - position.y_min; float factor = (e.wheel.y > 0) ? 0.9f : 1.1f; double new_height = range_y * factor; double aspect = (double)WINDOW_WIDTH / WINDOW_HEIGHT; double new_width = new_height * aspect; position.x_min = cr - (double)mx / (WINDOW_WIDTH - 1) * new_width; position.x_max = position.x_min + new_width; position.y_min = ci - (double)my / (WINDOW_HEIGHT - 1) * new_height; position.y_max = position.y_min + new_height; pos_updated = true; } if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN) { if (e.button.button == SDL_BUTTON_LEFT) { is_dragging = true; drag_start_x = (float)e.button.x; drag_start_y = (float)e.button.y; // store current view drag_start_x_min = position.x_min; drag_start_x_max = position.x_max; drag_start_y_min = position.y_min; drag_start_y_max = position.y_max; } float mx, my; SDL_GetMouseState(&mx, &my); double cr = position.x_min + (position.x_max - position.x_min) * mx / (WINDOW_WIDTH - 1); double ci = position.y_min + (position.y_max - position.y_min) * my / (WINDOW_HEIGHT - 1); int iter; double zr, zi; mandelbrot_at_point(cr, ci, &iter, &zr, &zi); snprintf(debug_text, sizeof(debug_text), "c = %.6f %+.6fi | z = %.6f %+.6fi | iteration %d", cr, ci, zr, zi, iter); } if (e.type == SDL_EVENT_MOUSE_MOTION) { if (is_dragging) { float mx = (float)e.motion.x; float my = (float)e.motion.y; double dx = mx - drag_start_x; double dy = my - drag_start_y; double view_width = drag_start_x_max - drag_start_x_min; double view_height = drag_start_y_max - drag_start_y_min; // Pan: shift the view opposite to mouse movement double x_shift = dx * view_width / (WINDOW_WIDTH - 1); double y_shift = dy * view_height / (WINDOW_HEIGHT - 1); position.x_min = drag_start_x_min - x_shift; position.x_max = drag_start_x_max - x_shift; position.y_min = drag_start_y_min - y_shift; position.y_max = drag_start_y_max - y_shift; pos_updated = true; } } if (e.type == SDL_EVENT_MOUSE_BUTTON_UP) { if (e.button.button == SDL_BUTTON_LEFT) { is_dragging = false; } } } if (pos_updated) { // calculate_points(&position); calculate_points_pthread(&position); pos_updated = false; } draw_points(state->surface); if (debug_text[0] != '\0') { SDL_SetRenderDrawColor(state->renderer, 64, 224, 208, 255); SDL_RenderDebugText(state->renderer, 10, 10, debug_text); } SDL_RenderPresent(state->renderer); SDL_UpdateWindowSurface(state->window); SDL_Delay(16); } SDL_DestroyRenderer(state->renderer); SDL_DestroyWindow(state->window); SDL_Quit(); return 0; }