794 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
			
		
		
	
	
			794 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
| /** READ BEFORE JUDING!
 | |
|  * Yes, I know this code is a mess. Debug code is added
 | |
|  * happhazardly, two cameras are used, etc.
 | |
|  * That's because it's a temporary program
 | |
|  * to create optimizations and debug rendering issues without hardware.
 | |
|  * None of this is going to be included in the project, and the code is thus
 | |
|  * not extensible or organized; it really doesn't save any effort to do so.
 | |
|  *
 | |
|  * This code is meant for my eyes only. You've been warned!
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <stdlib.h>
 | |
| #include <stdio.h>
 | |
| #include <stdint.h>
 | |
| #include <raylib.h>
 | |
| #include <raymath.h>
 | |
| #include <limits.h>
 | |
| #include <complex.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| #include <time.h>
 | |
| 
 | |
| #define WINDOW_SIZE_X     1600
 | |
| #define WINDOW_SIZE_Y     800
 | |
| #define RES_X             160
 | |
| #define RES_Y             80
 | |
| #define DEFAULT_CENTER_X  0
 | |
| #define DEFAULT_CENTER_Y  0
 | |
| #define MOUSE_BUTTON      0
 | |
| #define STEP_SIZE         .1
 | |
| #define ZOOM_SIZE         .1
 | |
| 
 | |
| 
 | |
| #define DECIMAL_LOC           28
 | |
| #define DOUBLE_SCALER         (1 << DECIMAL_LOC)
 | |
| #define DOUBLE_TO_FIXED(val)  (int32_t)((val) * DOUBLE_SCALER)
 | |
| #define FIXED_MULTIPLY(x,y)   ((((uint64_t)(x))*(y)) >> DECIMAL_LOC)
 | |
| #define FIXED_TO_DOUBLE(val)  ((val) / (double)DOUBLE_SCALER)
 | |
| 
 | |
| #define INFTY     2
 | |
| #define INFTY_SQR INFTY * INFTY
 | |
| #define ITERS     255
 | |
| #define INFTY_SQR_FIXED DOUBLE_TO_FIXED(INFTY_SQR)
 | |
| 
 | |
| //#define SHIP
 | |
| //#undef SHIP
 | |
| //#define COLOR_DEBUG
 | |
| 
 | |
| Color get_color_dbg(int i) {
 | |
|   if(i == ITERS) return (Color){0,0,255,255};
 | |
| //  if(i == 0) return (Color){255,255,255,255};
 | |
|   return (Color){255,0,255,255};
 | |
| }
 | |
|   
 | |
| 
 | |
| #ifdef COLOR_DEBUG
 | |
| #elif SHIP
 | |
| Color get_color(int i) {
 | |
|   if(i == ITERS) return (Color){0, 0, 0, 255};
 | |
|   if(i == 0) return (Color){0, 0, 0, 255};
 | |
|     return (Color) {
 | |
|         2*(i - 128)+255,
 | |
|           0,
 | |
|           0,
 | |
|         255
 | |
|     };
 | |
| }
 | |
| #else
 | |
| Color get_color(int i) {
 | |
| //  if((i == ITERS) || (i == 0)) return (Color){0, 0, 0, 255};
 | |
|   if(i == ITERS) return (Color){0,0,0,255};
 | |
|   if(i == 0) return (Color){0,0,1,255};
 | |
|   if(i < 128) {
 | |
|     return (Color) {
 | |
|         (8*(i - 128)+255) & 0xff,
 | |
|         0,
 | |
|         (16*(i - 64)+255) & 0xff,
 | |
|         255
 | |
|     };
 | |
|   }
 | |
|     return (Color) {
 | |
|       0,
 | |
|         0,
 | |
|       ((unsigned int)-2*(i - 128)+255) & 0xff,
 | |
|         255
 | |
|     };
 | |
| }
 | |
| #endif
 | |
| 
 | |
| //C does remainder, not modulo.
 | |
| //TODO optimize for mod 8
 | |
| inline int mod(int n, int d) {
 | |
|   int r = n % d;
 | |
|   return (r < 0) ? r + d : r;
 | |
| }
 | |
| int mod(int n, int d);
 | |
| 
 | |
| 
 | |
| struct camera {
 | |
|   double min_r, min_i, max_r, max_i;
 | |
| };
 | |
| 
 | |
| typedef struct {
 | |
|   int32_t r; int32_t i;
 | |
| } FixedCord;
 | |
| 
 | |
| static inline int iterate(FixedCord c) {
 | |
|   int32_t z_i = 0;
 | |
|   int32_t z_r = 0;
 | |
|   int32_t z_r_2, z_i_2, zn_r, zn_i;
 | |
|   for(int it = 0; it < ITERS; it++) {
 | |
|     z_r_2 = FIXED_MULTIPLY(z_r, z_r);
 | |
|     z_i_2 = FIXED_MULTIPLY(z_i, z_i);
 | |
| 
 | |
|     zn_r = z_r_2 - z_i_2 + c.r;
 | |
| 
 | |
| #ifdef SHIP
 | |
|     zn_i = abs(FIXED_MULTIPLY((DOUBLE_TO_FIXED(2)), (FIXED_MULTIPLY(z_r, z_i)))) + c.i;
 | |
| #else
 | |
|     zn_i = (FIXED_MULTIPLY((DOUBLE_TO_FIXED(2)), (FIXED_MULTIPLY(z_r, z_i)))) + c.i;
 | |
| #endif
 | |
| 
 | |
|     z_i = zn_i;
 | |
|     z_r = zn_r;
 | |
| 
 | |
|     if(z_i_2 + z_r_2 > INFTY_SQR_FIXED) return it;
 | |
|   }
 | |
|   return ITERS;
 | |
| }
 | |
| 
 | |
| //blllluuuuurg, matracies and vectors in raylib are floats and we need doubles
 | |
| void shift_cam(struct camera *cam, double step_r, double step_i) {
 | |
|   double i_offset = (cam->max_i - cam->min_i) * step_i;
 | |
|   double r_offset = (cam->max_r - cam->min_r) * step_r;
 | |
|   cam->min_i += i_offset;
 | |
|   cam->max_i += i_offset;
 | |
|   cam->min_r += r_offset;
 | |
|   cam->max_r += r_offset;
 | |
| }
 | |
| 
 | |
| void zoom_cam(struct camera *cam, double zoom) {
 | |
|   double i_scale = (cam->max_i - cam->min_i) * zoom;
 | |
|   double r_scale = (cam->max_r - cam->min_r) * zoom;
 | |
|   cam->min_i += i_scale;
 | |
|   cam->max_i -= i_scale;
 | |
|   cam->min_r += r_scale;
 | |
|   cam->max_r -= r_scale;
 | |
| }
 | |
| 
 | |
| enum DIRECTIONS {
 | |
|   N, NE, E, SE, S, SW, W, NW
 | |
| };
 | |
| 
 | |
| 
 | |
| //we can inline these if needed
 | |
| 
 | |
| inline bool bitarray_check(uint8_t *array, size_t i) {
 | |
|   return array[i/8] & (1 << (i%8));
 | |
| }
 | |
| 
 | |
| inline void bitarray_set(uint8_t *array, size_t i) {
 | |
|   array[i/8] |= (1 << (i%8));
 | |
| }
 | |
| 
 | |
| inline FixedCord get_neighbor_coord(FixedCord from_coord, int direction, FixedCord step) {
 | |
|   if((direction == NW) || (direction < E)) from_coord.i += step.i;
 | |
|   if((direction > N) && (direction < S)) from_coord.r += step.r;
 | |
|   if((direction > E) && (direction < W)) from_coord.i -= step.i;
 | |
|   if(direction > S) from_coord.r -= step.r;
 | |
|   return from_coord;
 | |
| }
 | |
| FixedCord get_neighbor_coord(FixedCord from_coord, int direction, FixedCord step);
 | |
| 
 | |
| 
 | |
| size_t get_neighbor_index(size_t from_pixel, int direction) {
 | |
|   const int neighbor_index_accl[8] = 
 | |
|     {-RES_X, -RES_X + 1, 1, RES_X + 1, RES_X, RES_X - 1, -1, -RES_X - 1};
 | |
|   from_pixel += neighbor_index_accl[direction];
 | |
|   //canidate for optimization; lots of branches. maybe inline
 | |
|   return from_pixel;
 | |
| }
 | |
| 
 | |
| //we'll be storing info in the green channel to utalize available memory 
 | |
| //per pixel.
 | |
| 
 | |
| #define BACKSTACK_SIZE      32
 | |
| #define GCHAN_UNRENDERED    0 //don't change; green channel zero'd on cam move
 | |
| #define GCHAN_BLOCKED       (1 << 7) //interior element or visiteed
 | |
| #define GCHAN_INTERNAL      (1 << 5) //part of set, 0x20
 | |
| #define GCHAN_EXTERNAL      (1 << 0) //not part of set, 0x10
 | |
| #define GCHAN_INNER_VISITED (1 << 3)
 | |
| #define GCHAN_INNER_CLOSED  (1 << 2)
 | |
| 
 | |
| 
 | |
| /**
 | |
| void switch_pixel(coord &this_coord, const coord step, size_t this_index, int dir) {
 | |
| 
 | |
| }
 | |
| **/
 | |
| 
 | |
| void debug_step(Color *pix, Texture *tex, size_t index, bool pause) {
 | |
|   return;
 | |
| //        SetTargetFPS(0);
 | |
|   static bool fuckin_manual_pause_iguess = false;
 | |
|   static Camera2D cam = {0};
 | |
|   if(!cam.zoom) cam.zoom = (float)GetRenderWidth()/RES_X;
 | |
|   static int debug_color = 0;
 | |
|   const float dbg_cam_step = 100;
 | |
|   const float dbg_cam_zoom = 1.5;
 | |
| 
 | |
|   (pause || fuckin_manual_pause_iguess) ? SetTargetFPS(60) : SetTargetFPS(0);
 | |
| 
 | |
|   
 | |
| 
 | |
|   for(;;) {
 | |
|     switch(GetKeyPressed()) {
 | |
|       case KEY_UP:
 | |
|         cam.offset.y += dbg_cam_step;
 | |
|         break;
 | |
|       case KEY_DOWN:
 | |
|         cam.offset.y -= dbg_cam_step;
 | |
|         break;
 | |
|       case KEY_RIGHT:
 | |
|         cam.offset.x += dbg_cam_step;
 | |
|         break;
 | |
|       case KEY_LEFT:
 | |
|         cam.offset.x -= dbg_cam_step;
 | |
|         break;
 | |
|       case KEY_W:
 | |
|         cam.zoom *= dbg_cam_zoom;
 | |
|         break;
 | |
|       case KEY_S:
 | |
|         cam.zoom /= dbg_cam_zoom;
 | |
|         break;
 | |
|       case KEY_SPACE:
 | |
|         Vector2 mouse_pos = Vector2Multiply(GetMousePosition(), (Vector2){(double)RES_X / WINDOW_SIZE_X, (double)RES_Y / WINDOW_SIZE_Y});
 | |
|         //mouse_pos = Vector2Divide(mouse_pos, (Vector2){cam.zoom, cam.zoom});
 | |
|         printf("%f, %f (%lu)\n", mouse_pos.x, mouse_pos.y, ((size_t)mouse_pos.y * RES_X) + (size_t)mouse_pos.x);
 | |
|         break;
 | |
|       case KEY_ENTER:
 | |
|         return;
 | |
|       default:
 | |
|         BeginDrawing();
 | |
|         pix[index] = 
 | |
|           (Color) {debug_color, pix[index].g, 0, 255};
 | |
|         BeginDrawing();
 | |
|         UpdateTexture(*tex, pix);
 | |
|         DrawTextureEx(*tex, (Vector2)
 | |
|             {0 - cam.offset.x, cam.offset.y}, 0, 
 | |
|             cam.zoom, WHITE);
 | |
|         EndDrawing();
 | |
|         if(!pause && !fuckin_manual_pause_iguess) return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| //only need four indecies, however we use 8 because it's more convinient. 
 | |
| void detect_borders(bool borders[8], size_t i) {
 | |
|   //if this is too slow, it's easy to do it without the modulos.
 | |
|   int index_mod = i % RES_X;
 | |
|   bzero(borders, sizeof(*borders) * 8);
 | |
|   if((i + RES_X) > (RES_X * RES_Y)) {
 | |
|     for(int nei_dir = SE; nei_dir <= SW; nei_dir++)
 | |
|       borders[nei_dir] = GCHAN_EXTERNAL;
 | |
|   }
 | |
|   else if(((int)i - RES_X) < 0) {
 | |
|     borders[NE] = GCHAN_EXTERNAL;
 | |
|     borders[N] = GCHAN_EXTERNAL;
 | |
|     borders[NW] = GCHAN_EXTERNAL;
 | |
|   }
 | |
|   if(index_mod == 0) {
 | |
|     for(int nei_dir = SW; nei_dir < NW; nei_dir++)
 | |
|       borders[nei_dir] = GCHAN_EXTERNAL;
 | |
|   }
 | |
|   else if(index_mod == (RES_X - 1)) {
 | |
|     for(int nei_dir = NE; nei_dir < SE; nei_dir++)
 | |
|       borders[nei_dir] = GCHAN_EXTERNAL;
 | |
|   }
 | |
| 
 | |
|             /**
 | |
|             //runs into occational percision issues
 | |
|             if((this_coord.i - scale.i) <= cam_bord_fixed_s) {
 | |
|             for(int nei_dir = SE; nei_dir <= SW; nei_dir++)
 | |
|             nei_presort[nei_dir] = GCHAN_EXTERNAL;
 | |
|             }
 | |
|             else if((this_coord.i + scale.i) >= cam_bord_fixed_n) {
 | |
|             printf("bruh\n");
 | |
|             nei_presort[NE] = GCHAN_EXTERNAL;
 | |
|             nei_presort[N] = GCHAN_EXTERNAL;
 | |
|             nei_presort[NW] = GCHAN_EXTERNAL;
 | |
|             }
 | |
|             if((this_coord.r - scale.r) <= cam_bord_fixed_w) {
 | |
|             for(int nei_dir = SW; nei_dir < NW; nei_dir++)
 | |
|             nei_presort[nei_dir] = GCHAN_EXTERNAL;
 | |
|             }
 | |
|             else if((this_coord.r + scale.r) >= cam_bord_fixed_e) {
 | |
|             for(int nei_dir = NE; nei_dir < SE; nei_dir++)
 | |
|             nei_presort[nei_dir] = GCHAN_EXTERNAL;
 | |
|             }
 | |
|              **/
 | |
| }
 | |
| 
 | |
| void debug_nei_arrays(int *priority, int *presort, size_t index) {
 | |
|   int debug_x = index % RES_X;
 | |
|   int debug_y = index / RES_X;
 | |
|   printf("(%i, %i) %lu: pre [", debug_x, debug_y, index);
 | |
|   for(int nd = 0; nd < 8; nd++) printf("%i, ", presort[nd]);
 | |
|   printf("], pri [");
 | |
|   for(int nd = 0; nd < 8; nd++) printf("%i, ", priority[nd]);
 | |
|   printf("]\n");
 | |
| }
 | |
| 
 | |
| enum {
 | |
|   SCAN_MODE_NONE,
 | |
|   SCAN_MODE_SAFE,
 | |
|   SCAN_MODE_INTERIOR
 | |
| } scan_mode;
 | |
| 
 | |
| unsigned int mandelbrot_bordertrace(struct camera *cam, Color *pixels) {
 | |
|   //these lookup tables r cheap cuz on the stm32f1, 1 memory read is 1 instruction
 | |
|   FixedCord scale = { 
 | |
|     .r = DOUBLE_TO_FIXED((cam->max_r - cam->min_r) / (double)RES_X), 
 | |
|     .i = DOUBLE_TO_FIXED((cam->max_i - cam->min_i) / (double)RES_Y)};
 | |
|   FixedCord c = {.r = 0, .i = DOUBLE_TO_FIXED(cam->max_i)};
 | |
|   unsigned int total_iters = 0;
 | |
|   size_t on_pixel = 0;
 | |
|   int border_scanning = 0;
 | |
| 
 | |
|   
 | |
| 
 | |
|   Image img = GenImageColor(RES_X, RES_Y, BLUE);
 | |
|   Texture debug_tex = LoadTextureFromImage(img);
 | |
|   UnloadImage(img);
 | |
| //  bzero(pixels, RES_X * RES_Y * sizeof(Color));
 | |
| //  for(size_t c = 0; c < (RES_X * RES_Y); c++) pixels[c] = (Color){0,0,0,255};
 | |
|   
 | |
| 
 | |
|   for(int y = 0; y < RES_Y; y++) {
 | |
|     border_scanning = 0;
 | |
|     for(int x = 0; x < RES_X; x++) {
 | |
| 
 | |
|       c.r = DOUBLE_TO_FIXED((((on_pixel % RES_X) / (double)RES_X) * (cam->max_r - cam->min_r)) + cam->min_r);
 | |
|       c.i = DOUBLE_TO_FIXED((((on_pixel / (double)RES_X) / (double)RES_Y) * (cam->min_i - cam->max_i)) + cam->max_i);
 | |
|         
 | |
|       /**
 | |
|       SetTargetFPS(0);
 | |
|       BeginDrawing();
 | |
|       UpdateTexture(debug_tex, pixels);
 | |
|       DrawTextureEx(debug_tex, (Vector2){0,0}, 0, (float)GetRenderWidth()/RES_X, WHITE);
 | |
|       EndDrawing();
 | |
|       **/
 | |
| 
 | |
|       switch(pixels[on_pixel].g) {
 | |
|         case GCHAN_INTERNAL:
 | |
| //          printf("starting interior  trace...\n");
 | |
|           size_t inner_pix_i = on_pixel;
 | |
|           int next_pix;
 | |
|           bool pass = false;
 | |
|           bool start_detached = false;
 | |
|           bool touching_start;
 | |
|           bool borders[8];
 | |
|           int nei_dir;
 | |
|           size_t nei_i;
 | |
| 
 | |
|           bool trusted_nei[8];
 | |
|           
 | |
|           {
 | |
|             //TODO
 | |
|             detect_borders(borders, on_pixel);
 | |
|             for(nei_dir = 0; nei_dir < 8; nei_dir += 2) if(borders[nei_dir]) break; 
 | |
| 
 | |
| 
 | |
|             int edge_state = 0;
 | |
|             bool first_unrendered = false;
 | |
|             for(nei_dir = 0; nei_dir < 8; nei_dir += 2) {
 | |
|               nei_i = get_neighbor_index(inner_pix_i, nei_dir);
 | |
|               if((nei_dir == 0) && (pixels[nei_i].g == GCHAN_UNRENDERED)) first_unrendered = true;
 | |
|               if((edge_state & 1) && (pixels[nei_i].g != GCHAN_UNRENDERED)) edge_state++;
 | |
|               else if(!(edge_state & 1) && (pixels[nei_i].g == GCHAN_UNRENDERED)) edge_state++;
 | |
|             }
 | |
|             //tired, easier to think of success states
 | |
|             if(!((edge_state == 1)
 | |
|                 || ((edge_state == 2) && (!first_unrendered && pixels[nei_i].g != GCHAN_UNRENDERED))
 | |
|                 || ((edge_state == 3) && (first_unrendered && pixels[nei_i].g == GCHAN_UNRENDERED)))) break;
 | |
|           }
 | |
|           for(nei_dir = 0; nei_dir < 8; nei_dir++) {
 | |
|             nei_i = get_neighbor_index(inner_pix_i, nei_dir);
 | |
|             if(pixels[nei_i].g == GCHAN_UNRENDERED) trusted_nei[nei_dir] = true;
 | |
|             else trusted_nei[nei_dir] = false;
 | |
|           }
 | |
| 
 | |
|           while(true) {
 | |
|             detect_borders(borders, inner_pix_i);
 | |
| 
 | |
|             next_pix = -1;
 | |
|             touching_start = false;
 | |
|             //debug_step(pixels, &debug_tex, inner_pix_i, true);
 | |
|             for(nei_dir = 0; nei_dir < 8; nei_dir += 2) {
 | |
|               size_t nei_i;
 | |
|               size_t localized_dirs[8];
 | |
| 
 | |
|               if(borders[nei_dir]) continue;
 | |
|               nei_i = get_neighbor_index(inner_pix_i, nei_dir);
 | |
|               if(nei_i == on_pixel) {
 | |
|                 touching_start = true;
 | |
|                 if(start_detached) {
 | |
|                   next_pix = on_pixel;
 | |
|                   break;
 | |
|                 }
 | |
|                 continue;
 | |
|               }
 | |
|               if(pixels[nei_i].g != ((pass) ? GCHAN_INNER_VISITED : GCHAN_INTERNAL)) continue;
 | |
|               //if((pixels[nei_i].g == GCHAN_UNRENDERED) || (pixels[nei_i].g == ((pass) ? GCHAN_INNER_CLOSED : GCHAN_INNER_VISITED))) continue;
 | |
| 
 | |
|               for(int i = 0; i < 8; i++) localized_dirs[i] = mod(nei_dir + i, 8);
 | |
|               if(!(trusted_nei[localized_dirs[2]] || trusted_nei[localized_dirs[1]] ||
 | |
|                    trusted_nei[localized_dirs[6]] || trusted_nei[localized_dirs[7]])) {
 | |
|                 continue;
 | |
|               }
 | |
|               /** TODO if we have time, we can only keep track of safe borders when we get in trouble by looking at
 | |
|               the past pixel. This has a lot of overhead, but I did it before thinking & I'm out of resources.
 | |
|               **/
 | |
|               trusted_nei[localized_dirs[4]] = false;
 | |
|               trusted_nei[localized_dirs[3]] = trusted_nei[localized_dirs[2]];
 | |
|               trusted_nei[localized_dirs[2]] = trusted_nei[localized_dirs[1]];
 | |
|               trusted_nei[localized_dirs[5]] = trusted_nei[localized_dirs[6]];
 | |
|               trusted_nei[localized_dirs[6]] = trusted_nei[localized_dirs[7]];
 | |
|               {
 | |
|                 bool front_neighbors[5];
 | |
|                 bool local_borders[8];
 | |
|                 detect_borders(local_borders, nei_i);
 | |
|                 for(int nei_edge_dir = -1; nei_edge_dir <= 1; nei_edge_dir++) {
 | |
|                   size_t nei_edge_local = mod(nei_dir + nei_edge_dir, 8);
 | |
|                   front_neighbors[nei_edge_dir + 1] = 
 | |
|                     !(local_borders[nei_edge_local] || (pixels[get_neighbor_index(nei_i, nei_edge_local)].g != GCHAN_UNRENDERED));
 | |
|                 }
 | |
|                 front_neighbors[3] = trusted_nei[localized_dirs[6]];
 | |
|                 front_neighbors[4] = trusted_nei[localized_dirs[2]];
 | |
|                 trusted_nei[localized_dirs[7]] = (front_neighbors[0] && (front_neighbors[3] || 
 | |
|                     (front_neighbors[1] && front_neighbors[2] && front_neighbors[4])));
 | |
|                 trusted_nei[localized_dirs[1]] = (front_neighbors[2] && (front_neighbors[4] || 
 | |
|                     (front_neighbors[0] && front_neighbors[1] && front_neighbors[3])));
 | |
|                 trusted_nei[localized_dirs[0]] = (front_neighbors[1] && (trusted_nei[localized_dirs[1]] || 
 | |
|                       trusted_nei[localized_dirs[7]]));
 | |
|               }
 | |
|               next_pix = nei_i;
 | |
|               break;
 | |
|             }
 | |
|             if(!start_detached && !touching_start && !(inner_pix_i == on_pixel)) start_detached = true;
 | |
|             else if(start_detached && touching_start) {
 | |
|               start_detached = false;
 | |
|               if(pass) {
 | |
|                 pixels[inner_pix_i].g = GCHAN_INNER_CLOSED;
 | |
|                 pixels[touching_start].g = GCHAN_INNER_CLOSED;
 | |
|                 border_scanning = true;
 | |
|                 debug_step(pixels, &debug_tex, inner_pix_i, true);
 | |
|                 break;
 | |
|               }
 | |
|               else {
 | |
|                 pass = true;
 | |
|                 pixels[inner_pix_i].g = GCHAN_INNER_VISITED;
 | |
|                 inner_pix_i = on_pixel;
 | |
|                 continue;
 | |
|               }
 | |
|             }
 | |
|             pixels[inner_pix_i].g = ((pass) ? GCHAN_INNER_CLOSED : GCHAN_INNER_VISITED);
 | |
|             //TODO remove
 | |
|             if(pass) pixels[next_pix].r = 0xff;
 | |
|             else { pixels[next_pix].b = 0xaa; }
 | |
| 
 | |
|             if(next_pix < 0) {
 | |
|               debug_step(pixels, &debug_tex, inner_pix_i, true);
 | |
|               break;
 | |
|             }
 | |
|             else {
 | |
|               inner_pix_i = next_pix;
 | |
|             }
 | |
|           }
 | |
|           break;
 | |
|         case GCHAN_UNRENDERED:
 | |
|           if(border_scanning) {
 | |
|             //pixels[on_pixel] = get_color(ITERS);
 | |
|             //printf("interior\n");
 | |
|             pixels[on_pixel] = (Color){0xfe,0,0xfe,0xff};
 | |
|             break;
 | |
|           }
 | |
|           //printf("rendering %i, %i (%lu)\n", x, y, on_pixel);
 | |
|           int i = iterate(c);
 | |
|           total_iters += i;
 | |
|           pixels[on_pixel] = get_color(i);
 | |
|           if(i == ITERS) {
 | |
|             FixedCord this_coord = c;
 | |
|             size_t this_index = on_pixel;
 | |
|             bool seperated_from_start = false;
 | |
|             int nei_priority[8];
 | |
|             int last_nei_priority[8];
 | |
|             int nei_presort[8];
 | |
| 
 | |
|             size_t backstack[BACKSTACK_SIZE];
 | |
|             size_t backstack_i = 0;
 | |
|             int backstack_calls = 0;
 | |
| 
 | |
|             int nei_dir;
 | |
| 
 | |
|             debug_step(pixels, &debug_tex, this_index, false);
 | |
|             bool debug_mode = false;
 | |
| 
 | |
| 
 | |
|             
 | |
|             bool borders[8];
 | |
|             detect_borders(borders, inner_pix_i);
 | |
|             for(nei_dir = 0; nei_dir < 8; nei_dir++) {
 | |
|               size_t nei_i;
 | |
|               if(borders[nei_dir]) break;
 | |
|               nei_i = get_neighbor_index(on_pixel, nei_dir);
 | |
|               if(pixels[nei_i].g & GCHAN_EXTERNAL) break;
 | |
|             }
 | |
| 
 | |
|             if(nei_dir >= 8) {
 | |
|               border_scanning = SCAN_MODE_INTERIOR;
 | |
|               break;
 | |
|             }
 | |
| 
 | |
|             while(true) {
 | |
|               detect_borders(borders, this_index);
 | |
|               debug_step(pixels, &debug_tex, this_index, false);
 | |
|               if(debug_mode) debug_step(pixels, &debug_tex, on_pixel, debug_mode);
 | |
|               
 | |
|               //step 1: check pixels around us, fill in neighbors.
 | |
|               bzero(nei_presort, sizeof(nei_presort));
 | |
| 
 | |
|               this_coord.r = DOUBLE_TO_FIXED((((this_index % RES_X) / (double)RES_X) * (cam->max_r - cam->min_r)) + cam->min_r);
 | |
|               this_coord.i = DOUBLE_TO_FIXED((((this_index / (double)RES_X) / (double)RES_Y) * (cam->min_i - cam->max_i)) + cam->max_i);
 | |
|               /** now fill in neighbor info based on green channel,
 | |
|                *  iterate if no info available.
 | |
|                *  if this is to slow we could flatten this; it's predictable
 | |
|                *  where there will be info
 | |
|                **/  
 | |
|               // TODO replace modulos with bitwise ops 
 | |
|               bool start_is_nei = false;
 | |
|               for(int nei_dir = 0; nei_dir < 8; nei_dir++) {
 | |
|                 size_t nei_i;
 | |
|                 uint8_t gchan_info;
 | |
| 
 | |
|                 //happens if we're pushed against the screen
 | |
|                 if(borders[nei_dir]) {
 | |
|                   nei_presort[nei_dir] = GCHAN_EXTERNAL;
 | |
|                   continue;
 | |
|                 }
 | |
| 
 | |
|                 nei_i = get_neighbor_index(this_index, nei_dir);
 | |
|                 gchan_info = pixels[nei_i].g;
 | |
|                 if(nei_i == on_pixel) start_is_nei = true;
 | |
|                 //note that when we move this over, there will be no alpha channel.
 | |
|                 //gchan_info will be extracted differently!!!
 | |
|                 if(gchan_info) nei_presort[nei_dir] = gchan_info;
 | |
|                 else {
 | |
|                   int i = iterate(get_neighbor_coord(this_coord, nei_dir, scale));
 | |
|                   pixels[nei_i] = get_color(i);
 | |
|                   if(i == ITERS) nei_presort[nei_dir] = GCHAN_INTERNAL;
 | |
|                   else {
 | |
|                     //exterior
 | |
|                     nei_presort[nei_dir] = GCHAN_EXTERNAL;
 | |
|                   }
 | |
|                   pixels[nei_i].g = nei_presort[nei_dir];
 | |
|                 }
 | |
|               }
 | |
|               if(!start_is_nei && !seperated_from_start && (this_index != on_pixel)) seperated_from_start = true;
 | |
|               if(start_is_nei && seperated_from_start) {
 | |
|                 //printf("success!\n");
 | |
|                 pixels[this_index].g = GCHAN_BLOCKED;
 | |
|                 break;
 | |
|               }
 | |
| 
 | |
|               //go back if we're in the interior and not an edge
 | |
|               int edge_cnt = 0;
 | |
|               //sort into prioraties
 | |
|               for(int nei_dir = 0; nei_dir < 8; nei_dir += 2) {
 | |
|                 int nei_edge_i;
 | |
|                 if(nei_presort[nei_dir] != GCHAN_INTERNAL) {
 | |
|                   nei_priority[nei_dir] = -1;
 | |
|                   continue; 
 | |
|                 }
 | |
| 
 | |
|                 //TODO rename nei_edge_i
 | |
|                 //printf("%i: \n", nei_dir);
 | |
|                 for(nei_edge_i = -2; nei_edge_i <= 2; nei_edge_i++) {
 | |
|                   int nei_edge_mod = mod((nei_dir + nei_edge_i), 8);
 | |
|                   if((nei_presort[nei_edge_mod] == GCHAN_EXTERNAL) || borders[nei_edge_mod]) break;
 | |
|                 }
 | |
|                 if(nei_edge_i > 2) {
 | |
|                   //no edge found
 | |
|                   nei_priority[nei_dir] = -2; //TODO test; remove interior check if nessesary
 | |
|                   continue;
 | |
|                 }
 | |
| 
 | |
|                 //narrow bridge scenario
 | |
|                 if(nei_presort[mod((nei_dir + 1), 8)] & nei_presort[mod((nei_dir - 1), 8)] & GCHAN_EXTERNAL) {
 | |
|                   nei_i = get_neighbor_index(this_index, nei_dir);
 | |
|                   //pixels[nei_i] = (Color) {0xff, pixels[nei_i].g, 0x00, 0xff};
 | |
|                   nei_priority[nei_dir] = -1;
 | |
|                   continue;
 | |
|                 }
 | |
| 
 | |
|                 edge_cnt++;
 | |
|                 nei_priority[nei_dir] = 0;
 | |
|               }
 | |
|               if(edge_cnt >= 2) { 
 | |
|                 backstack[backstack_i++ % BACKSTACK_SIZE] = this_index;
 | |
|                 //printf("backstack increased\n");
 | |
|               }
 | |
| 
 | |
|               //now go to canidate with lowest prioraty
 | |
|               pixels[this_index].g = GCHAN_BLOCKED;
 | |
|               for(int priority = 0; priority <= 5; priority++) { //TODO we might not need the priority system anymore
 | |
|                 for(int nei_dir = 0; nei_dir < 8; nei_dir += 2) {
 | |
|                   if(nei_priority[nei_dir] != priority) continue;
 | |
|                   backstack_calls = 0;
 | |
|                   this_index = get_neighbor_index(this_index, nei_dir);
 | |
|                   this_coord = get_neighbor_coord(this_coord, nei_dir, scale);
 | |
|                   //printf("--> (%zu, %zu)\n", this_index % RES_X, this_index / RES_X);
 | |
|                   goto NEXT_PIXEL;
 | |
|                 }
 | |
|               }
 | |
|               if((backstack_calls++ > BACKSTACK_SIZE) || (backstack_i < 1)) { //please don't cause issues...
 | |
|                 //printf("cycled through backstack, questionable success...\n");
 | |
|                 break;
 | |
|               }
 | |
|               this_index = backstack[--backstack_i % BACKSTACK_SIZE];
 | |
|               NEXT_PIXEL:
 | |
|               memcpy(last_nei_priority, nei_priority, sizeof(nei_priority));
 | |
|             }
 | |
|             debug_step(pixels, &debug_tex, this_index, true);
 | |
|           }
 | |
|           else pixels[on_pixel].g = GCHAN_EXTERNAL;
 | |
|           break;
 | |
|         case GCHAN_INNER_CLOSED:
 | |
|           //printf("bruh\n");
 | |
|           if(((x + 2) < RES_X) && (pixels[on_pixel + 1].g == GCHAN_UNRENDERED)) border_scanning = SCAN_MODE_NONE;
 | |
|           break;
 | |
|         default: 
 | |
|           border_scanning = SCAN_MODE_NONE;
 | |
|       }
 | |
|       on_pixel++;
 | |
|       c.r += scale.r;
 | |
|       //printf("%u\n", on_pixel);
 | |
|     }
 | |
|     border_scanning = false;
 | |
|   }
 | |
|   debug_step(pixels, &debug_tex, 0, true);
 | |
|   for(size_t i = 0; i < (RES_X * RES_Y); i++) pixels[i].g = 0;
 | |
|   return total_iters;
 | |
| }
 | |
| 
 | |
| unsigned int mandelbrot_unoptimized(struct camera *cam, Color *pixels) {
 | |
|   FixedCord scale = { 
 | |
|     .r = DOUBLE_TO_FIXED((cam->max_r - cam->min_r) / (double)RES_X), 
 | |
|     .i = DOUBLE_TO_FIXED((cam->max_i - cam->min_i) / (double)RES_Y)};
 | |
|   FixedCord c = { .r = DOUBLE_TO_FIXED(cam->min_r), .i = DOUBLE_TO_FIXED(cam->max_i) };
 | |
|   unsigned int total_iters = 0;
 | |
|   size_t on_pixel = 0;
 | |
|   for(int y = 0; y < RES_Y; y++) {
 | |
|     c.r = DOUBLE_TO_FIXED(cam->min_r);
 | |
|     for(int x = 0; x < RES_X; x++) {
 | |
|       int i = iterate(c);
 | |
|       c.r = DOUBLE_TO_FIXED((((on_pixel % RES_X) / (double)RES_X) * (cam->max_r - cam->min_r)) + cam->min_r);
 | |
|       c.i = DOUBLE_TO_FIXED((((on_pixel / (double)RES_X) / (double)RES_Y) * (cam->min_i - cam->max_i)) + cam->max_i);
 | |
|       total_iters += i;
 | |
|       pixels[((y * RES_X) + x)] = get_color(i);
 | |
|       on_pixel++;
 | |
| //      c.r += scale.r;      
 | |
|     }
 | |
|     c.i -= scale.i;
 | |
|   }
 | |
|   return total_iters;
 | |
| }
 | |
| 
 | |
| int main() {
 | |
|   //test();
 | |
|   //return 0;
 | |
|   Color *pixels_unoptimized = malloc(RES_X * RES_Y * sizeof(Color));
 | |
|   Color *pixels_optimized = malloc(RES_X * RES_Y * sizeof(Color));
 | |
|   bool optimized = true;
 | |
|   //(1.514379082621093886019, 0.000033222739567139065) - (1.514381385800912305228, 0.000034374329476534746)
 | |
| 
 | |
|   struct camera cam_default = {
 | |
|     .min_r = -1,
 | |
|     .max_r = 1
 | |
|   };
 | |
|   cam_default.min_i = ((double)RES_Y / RES_X) * cam_default.min_r;
 | |
|   cam_default.max_i = ((double)RES_Y / RES_X) * cam_default.max_r;
 | |
|     
 | |
| 
 | |
|   //done
 | |
|   //.min_r = 0.340060821337554164412, .min_i = -0.076399869494282027227, .max_r = 0.340671385211165078655, .max_i = -0.076094587557451340287 
 | |
| 
 | |
|   //done
 | |
|   //.min_r = 0.348347456407892719366, .min_i = -0.092130353675640097588, .max_r = 0.349033773135021985201, .max_i = -0.091787195312047098472
 | |
| 
 | |
| 
 | |
|   //has internal noise 
 | |
|   //.min_r = 0.348416088080605645949, .min_i = -0.092130353675640097588, .max_r = 0.349102404807734911785, .max_i = -0.091787195312047098472
 | |
| 
 | |
|   //needs diagnol transfer
 | |
|   //.min_r = 0.352126044212195454808, .min_i = -0.101818891004586714599, .max_r = 0.354169737175103083171, .max_i = -0.100797044523048578979
 | |
| 
 | |
|   //works
 | |
|   //.min_r = 1.514379082621093886019, .min_i = 0.000033222739567139065, .max_r = 1.514381385800912305228, .max_i = 0.000034374329476534746
 | |
| 
 | |
| //  unusual issue; complete rendered border
 | |
|   //  .min_r = 0.426539347230382670517, .min_i = 0.218210183100018217939, .max_r = 0.427445609943903681582, .max_i = 0.218663314456816582076 
 | |
| 
 | |
| 
 | |
|   
 | |
|   struct camera cam = {
 | |
|     //.min_r = 0.348416088080605645949, .min_i = -0.092130353675640097588, .max_r = 0.349102404807734911785, .max_i = -0.091787195312047098472
 | |
|     .min_r = 0.348347456407892719366, .min_i = -0.092130353675640097588, .max_r = 0.349033773135021985201, .max_i = -0.091787195312047098472
 | |
|   };
 | |
|   InitWindow(WINDOW_SIZE_X, WINDOW_SIZE_Y, "mandelbrot fixed point test");
 | |
|   SetTraceLogLevel(LOG_ERROR);
 | |
| 
 | |
|   Image img = GenImageColor(RES_X, RES_Y, BLUE);
 | |
|   Texture tex = LoadTextureFromImage(img);
 | |
|   UnloadImage(img);
 | |
| 
 | |
|   SetTargetFPS(60);
 | |
| 
 | |
|   while(!WindowShouldClose()) {
 | |
|     switch(GetKeyPressed()) {
 | |
|       case KEY_UP:
 | |
|         shift_cam(&cam, 0, STEP_SIZE);
 | |
|         break;
 | |
|       case KEY_DOWN:
 | |
|         shift_cam(&cam, 0, -STEP_SIZE);
 | |
|         break;
 | |
|       case KEY_RIGHT:
 | |
|         shift_cam(&cam, STEP_SIZE, 0);
 | |
|         break;
 | |
|       case KEY_LEFT:
 | |
|         shift_cam(&cam, -STEP_SIZE, 0);
 | |
|         break;
 | |
|       case KEY_W:
 | |
|         zoom_cam(&cam, ZOOM_SIZE);
 | |
|         break;
 | |
|       case KEY_S:
 | |
|         zoom_cam(&cam, -ZOOM_SIZE);
 | |
|         break;
 | |
|       case KEY_SPACE:
 | |
|         optimized = !optimized;
 | |
|         break;
 | |
|       default:
 | |
|         BeginDrawing();
 | |
|         EndDrawing();
 | |
|         continue;
 | |
|         break;
 | |
|     }
 | |
|     printf(".min_r = %.21f, .min_i = %.21f, .max_r = %.21f, .max_i = %.21f\n", cam.min_r, cam.min_i, cam.max_r, cam.max_i);
 | |
| 
 | |
| 
 | |
|     clock_t begin, end;
 | |
|     double time_unoptimized;
 | |
|     double time_optimized;
 | |
| 
 | |
|     for(int i = 0; i < (RES_X * RES_Y); i++) { pixels_unoptimized[i] = (Color){0, 0, 0, 0xff}; }
 | |
|     for(int i = 0; i < (RES_X * RES_Y); i++) { pixels_optimized[i] = (Color){0, 0, 0, 0xff}; }
 | |
| 
 | |
|     begin = clock();
 | |
|     unsigned int unoptimized_iters = mandelbrot_unoptimized(&cam, pixels_unoptimized);
 | |
|     end = clock();
 | |
| 
 | |
|     time_unoptimized = (double)(end - begin) / CLOCKS_PER_SEC;
 | |
|     printf("Unoptimized: %u iterations, %f seconds\n", unoptimized_iters, time_unoptimized);
 | |
| 
 | |
|     begin = clock();
 | |
|     unsigned int optimized_iters = mandelbrot_bordertrace(&cam, pixels_optimized);
 | |
|     end = clock();
 | |
| 
 | |
|     time_optimized = (double)(end - begin) / CLOCKS_PER_SEC;
 | |
|     printf("Border tracing: %u iterations, %f seconds\n", optimized_iters, time_optimized); 
 | |
| 
 | |
|     printf("border tracing does %f%% of nieve approach\n", ((float)optimized_iters / unoptimized_iters) * 100);
 | |
| 
 | |
|     BeginDrawing();
 | |
|     printf("%s\n", optimized ? "optimized mode" : "unoptimized mode");
 | |
|     UpdateTexture(tex, optimized ? pixels_optimized : pixels_unoptimized);
 | |
|     DrawTextureEx(tex, (Vector2){0,0}, 0, (float)GetRenderWidth()/RES_X, WHITE);
 | |
|     EndDrawing();
 | |
| 
 | |
|   }
 | |
| 
 | |
|   return 0;
 | |
| }
 |