Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@snipsnipsnip
Last active August 24, 2019 15:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save snipsnipsnip/525284 to your computer and use it in GitHub Desktop.
Save snipsnipsnip/525284 to your computer and use it in GitHub Desktop.
SDL plotter of mandelbrot set / julia set / burning ship fractal
#include "SDL.h"
#include <cstdarg>
#include <cmath>
#include <cctype>
#include <algorithm>
/*
left drag: move position
left double click: jump position
right drag: shift initial c
right click: change mode
right click + wheel up/down: change mode
left click + wheel up/down: adjust iterations
wheel up/down: zoom
wheel drag: zoom
wheel click: reset
*/
#ifndef M_PI
# define M_PI (atan(1.0) * 4)
#endif
#ifdef _MSC_VER
# define LOG(...) print_log('L', __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#else
# define LOG(...) print_log('L', __FILE__, __LINE__, __func__, __VA_ARGS__)
#endif
void print_log(char level, const char *file, unsigned int line, const char *func, const char *format, ...)
{
va_list args;
// fprintf(stderr, "[%c]", level);
fprintf(stderr, "%s(%d)%s ", file, line, func);
va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
fputc('\n', stderr);
}
void draw_dot(SDL_Surface *screen, int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
Uint32 pixel = SDL_MapRGB(screen->format, r, g, b);
int bpp = screen->format->BytesPerPixel;
Uint8 *p = ((Uint8 *)screen->pixels) + y * screen->pitch + x * bpp;
switch (bpp)
{
case 1:
*p = pixel;
break;
case 2:
*(Uint16 *)p = pixel;
break;
case 3:
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
p[0] = (pixel >> 16) & 0xff;
p[1] = (pixel >> 8) & 0xff;
p[2] = pixel & 0xff;
#else
p[0] = pixel & 0xff;
p[1] = (pixel >> 8) & 0xff;
p[2] = (pixel >> 16) & 0xff;
#endif
break;
case 4:
*(Uint32 *)p = pixel;
break;
}
}
void draw_dot_clipped(SDL_Surface *screen, int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
if (screen->w <= x || screen->h <= y) { return; }
draw_dot(screen, x, y, r, g, b);
}
/*
5x5 bitmap font by shinh (http://d.hatena.ne.jp/shinichiro_h/20060814#1155567183)
Licensed under the New BSD License.
*/
void draw_letter(SDL_Surface *screen, int x, int y, char c)
{
static const Uint32 font[] =
{
0x00000000, 0x00401084, 0x0000014a, 0x00afabea, 0x01fa7cbf, 0x01111111,
0x0126c8a2, 0x00000084, 0x00821088, 0x00221082, 0x015711d5, 0x00427c84,
0x00220000, 0x00007c00, 0x00400000, 0x00111110, 0x00e9d72e, 0x00421084,
0x01f2222e, 0x00e8b22e, 0x008fa54c, 0x01f87c3f, 0x00e8bc2e, 0x0042221f,
0x00e8ba2e, 0x00e87a2e, 0x00020080, 0x00220080, 0x00820888, 0x000f83e0,
0x00222082, 0x0042222e, 0x00ead72e, 0x011fc544, 0x00f8be2f, 0x00e8862e,
0x00f8c62f, 0x01f0fc3f, 0x0010bc3f, 0x00e8e42e, 0x0118fe31, 0x00e2108e,
0x00e8c210, 0x01149d31, 0x01f08421, 0x0118d771, 0x011cd671, 0x00e8c62e,
0x0010be2f, 0x01ecc62e, 0x0114be2f, 0x00f8383e, 0x0042109f, 0x00e8c631,
0x00454631, 0x00aad6b5, 0x01151151, 0x00421151, 0x01f1111f, 0x00e1084e,
0x01041041, 0x00e4210e, 0x00000144, 0x01f00000, 0x00000082, 0x0164a4c0,
0x00749c20, 0x00e085c0, 0x00e4b908, 0x0060bd26, 0x0042388c, 0x00c8724c,
0x00a51842, 0x00420080, 0x00621004, 0x00a32842, 0x00421086, 0x015ab800,
0x00949800, 0x0064a4c0, 0x0013a4c0, 0x008724c0, 0x00108da0, 0x0064104c,
0x00c23880, 0x0164a520, 0x00452800, 0x00aad400, 0x00a22800, 0x00111140,
0x00e221c0, 0x00c2088c, 0x00421084, 0x00622086, 0x000022a2,
};
Uint32 d = font[toupper(c) - ' '];
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
if ((d >> (i * 5 + j)) & 1)
{
draw_dot_clipped(screen, x + j, y + i, 255, 255, 255);
// top
if (i == 0 || !((d >> ((i - 1) * 5 + j)) & 1))
{
draw_dot_clipped(screen, x + j, y + i - 1, 0, 0, 0);
}
// bottom
if (i == 4 || !((d >> ((i + 1) * 5 + j)) & 1))
{
draw_dot_clipped(screen, x + j, y + i + 1, 0, 0, 0);
}
// left
if (j == 0 || !((d >> (i * 5 + j - 1)) & 1))
{
draw_dot_clipped(screen, x + j - 1, y + i, 0, 0, 0);
}
// right
if (j == 4 || !((d >> (i * 5 + j + 1)) & 1))
{
draw_dot_clipped(screen, x + j + 1, y + i, 0, 0, 0);
}
}
}
}
}
void draw_message(SDL_Surface *screen, int x, int y, const char *message)
{
for (int i = 0; message[i]; i++)
{
if (isprint(message[i]))
{
draw_letter(screen, x + i * 8, y, message[i]);
}
}
}
class MandelblotBoard
{
SDL_Surface *screen;
double scale, cx, cy, ix, iy, fw, fh, aspect_ratio;
int iteration;
Uint8 mode;
Uint32 elapsed;
public:
void set_screen(SDL_Surface *surface)
{
screen = surface;
fw = screen->w;
fh = screen->h;
aspect_ratio = fw / fh;
}
void reset()
{
scale = 1.0;
cx = 0.0;
cy = 0.0;
ix = 0.0;
iy = 0.0;
iteration = 10;
mode = 0;
LOG("ok");
}
void draw()
{
Uint32 begin = SDL_GetTicks();
plot();
elapsed = SDL_GetTicks() - begin;
}
/* getters */
template <typename F>
void report(F &print)
{
char line[64];
sprintf(line, "took %d ms.", elapsed);
print(line);
print("");
sprintf(line, "iteration: %d times", iteration);
print(line);
double fx = 0, fy = fh, tx = fw, ty = 0;
pixel_to_coord(&fx, &fy);
pixel_to_coord(&tx, &ty);
sprintf(line, "from: (%1.15lf, %1.15lf)", fx, fy);
print(line);
sprintf(line, "to: (%1.15lf, %1.15lf)", tx, ty);
print(line);
sprintf(line, "c: (%1.15lf, %1.15lf)", ix, iy);
print(line);
sprintf(line, "mode: %d / 16", mode + 1);
print(line);
print(is_julia() ? " julia" : " mandelbrot");
if (is_burning_ship()) { print(" burning-ship"); }
if (is_swapping_xy()) { print(" swap-xy"); }
print(is_cubic() ? " cubic" : " quadratic");
}
bool is_julia() const
{
return mode & 1;
}
bool is_burning_ship() const
{
return mode & 2;
}
bool is_swapping_xy() const
{
return mode & 4;
}
bool is_cubic() const
{
return mode & 8;
}
/* setters */
void next_mode()
{
mode++;
mode &= 15;
}
void previous_mode()
{
mode--;
mode &= 15;
}
void shift_initial(double dx, double dy)
{
double zx = 0;
double zy = 0;
pixel_to_coord(&zx, &zy);
pixel_to_coord(&dx, &dy);
ix += zx - dx;
iy += zy - dy;
}
void set_center(double x, double y)
{
pixel_to_coord(&x, &y);
cx = x;
cy = y;
}
void shift_center(double dx, double dy)
{
double zx = 0;
double zy = 0;
pixel_to_coord(&zx, &zy);
pixel_to_coord(&dx, &dy);
cx += zx - dx;
cy += zy - dy;
}
void more_iteration()
{
if (iteration < 30)
{
iteration++;
}
else
{
iteration *= 1.1;
}
}
void less_iteration()
{
if (iteration < 30)
{
iteration--;
}
else
{
iteration /= 1.1;
}
if (iteration < 1)
{
iteration = 1;
}
}
void zoom(double f)
{
scale *= pow(1.3, f / 50);
}
void zoom_in()
{
zoom(-50);
}
void zoom_out()
{
zoom(50);
}
private:
void plot()
{
double f = 255.0 / iteration;
for (int y = 0; y < screen->h; y++)
{
for (int x = 0; x < screen->w; x++)
{
double px = x;
double py = y;
pixel_to_coord(&px, &py);
double vx = 0;
double vy = 0;
Uint8 c = (Uint8)(dive(px, py, &vx, &vy) * f);
dot(x, y, m(vx), c, m(vy));
}
}
}
static Uint8 m(double x)
{
return (Uint8)((-atan(x) / M_PI + 0.5) * 100);
}
void pixel_to_coord(double *x, double *y) const
{
*x = (*x / fw - 0.5) * aspect_ratio * scale + cx;
*y = -(*y / fh - 0.5) * scale + cy;
}
void dot(int x, int y, Uint8 r, Uint8 g, Uint8 b)
{
draw_dot(screen, x, y, r, g, b);
}
double get_radius() const
{
return 4.0;
}
int dive(double x, double y, double *vx, double *vy) const
{
int i;
double zx = ix;
double zy = iy;
if (is_julia())
{
std::swap(x, zx);
std::swap(y, zy);
}
for (i = 0; i < iteration; i++)
{
double nx, ny;
if (is_cubic())
{
nx = zx * (zx * zx - 3 * zy * zy) + x;
ny = zy * (3 * zx * zx - zy * zy) + y;
}
else
{
nx = zx * zx - zy * zy + x;
ny = zx * zy * 2.0 + y;
}
if (is_burning_ship())
{
nx = fabs(nx);
ny = fabs(ny);
}
if (is_swapping_xy())
{
std::swap(nx, ny);
}
if (nx * nx + ny * ny > get_radius())
{
*vx = nx;
*vy = ny;
break;
}
zx = nx;
zy = ny;
}
return i;
}
};
class Loop
{
SDL_Surface *screen;
MandelblotBoard board;
bool cancel_next_right_buttonup;
bool cancel_next_middle_buttonup;
Uint32 last_left_click;
bool is_help_visible;
public:
void start()
{
init();
while (pump())
{
}
}
private:
void init()
{
SDL_Init(SDL_INIT_VIDEO);
reset();
init_video(640, 480);
draw();
is_help_visible = true;
}
void reset()
{
board.reset();
}
void toggle_help()
{
is_help_visible = !is_help_visible;
}
void draw()
{
board.draw();
draw_help();
SDL_Flip(screen);
}
void draw_help()
{
static const char * const lines[] =
{
"left drag: move position",
"left double click: jump position",
"right drag: shift c",
"right click: change mode",
"right click + wheel up/down: change mode",
"left click + wheel up/down: adjust iterations",
"wheel up/down: zoom",
"wheel drag: zoom",
"wheel click: reset",
"h: toggle this help",
};
class HelpPrinter
{
SDL_Surface *s;
int x, y;
public:
HelpPrinter(SDL_Surface *screen) : s(screen), x(10), y(10) {}
void operator()(const char *line)
{
draw_message(s, x, y, line);
y += 7;
}
};
if (!is_help_visible) { return; }
HelpPrinter print(screen);
for (int i = 0; i < SDL_arraysize(lines); i++)
{
print(lines[i]);
}
print("");
board.report(print);
}
void init_video(int w, int h)
{
screen = SDL_SetVideoMode(w, h, 0, SDL_DOUBLEBUF | SDL_HWSURFACE | SDL_RESIZABLE);
board.set_screen(screen);
SDL_WM_SetCaption("mandelbrot", "mandelbrot");
cancel_next_right_buttonup = false;
last_left_click = 0;
}
void handle(const SDL_Event &event)
{
switch (event.type)
{
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_h)
{
toggle_help();
draw();
}
break;
case SDL_VIDEORESIZE:
init_video(event.resize.w, event.resize.h);
draw();
break;
case SDL_MOUSEMOTION:
if (event.motion.state & SDL_BUTTON_LMASK)
{
board.shift_center(event.motion.xrel, event.motion.yrel);
draw();
}
else if (event.motion.state & SDL_BUTTON_RMASK)
{
cancel_next_right_buttonup = true;
board.shift_initial(event.motion.xrel, event.motion.yrel);
draw();
}
else if (event.motion.state & SDL_BUTTON_MMASK)
{
cancel_next_middle_buttonup = true;
board.zoom(event.motion.yrel);
draw();
}
break;
case SDL_MOUSEBUTTONUP:
switch (event.button.button)
{
case SDL_BUTTON_LEFT:
if (SDL_GetTicks() - last_left_click <= 300)
{
last_left_click = 0;
board.set_center(event.button.x, event.button.y);
draw();
}
else
{
last_left_click = SDL_GetTicks();
}
break;
case SDL_BUTTON_RIGHT:
if (cancel_next_right_buttonup)
{
cancel_next_right_buttonup = false;
break;
}
board.next_mode();
draw();
break;
case SDL_BUTTON_MIDDLE:
if (cancel_next_middle_buttonup)
{
cancel_next_middle_buttonup = false;
break;
}
reset();
draw();
break;
case SDL_BUTTON_WHEELUP:
if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LMASK)
{
board.more_iteration();
}
else if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_RMASK)
{
cancel_next_right_buttonup = true;
board.previous_mode();
}
else
{
board.zoom_in();
}
draw();
break;
case SDL_BUTTON_WHEELDOWN:
if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LMASK)
{
board.less_iteration();
}
else if (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_RMASK)
{
cancel_next_right_buttonup = true;
board.next_mode();
}
else
{
board.zoom_out();
}
draw();
break;
}
break;
default:
break;
}
}
bool pump()
{
SDL_Event event;
while (SDL_WaitEvent(&event))
{
switch (event.type)
{
case SDL_KEYUP:
if (event.key.keysym.sym == SDLK_ESCAPE ||
(event.key.keysym.mod & KMOD_ALT) && event.key.keysym.sym == SDLK_F4)
{
return false;
}
handle(event);
break;
case SDL_QUIT:
return false;
default:
handle(event);
break;
}
}
return true;
}
};
int main(int argc, char **argv)
{
class SDLContext
{
public:
~SDLContext()
{
SDL_Quit();
}
};
SDLContext sdl;
Loop loop;
loop.start();
return 0;
}
@snipsnipsnip
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment