Last active
August 24, 2019 15:59
-
-
Save snipsnipsnip/525284 to your computer and use it in GitHub Desktop.
SDL plotter of mandelbrot set / julia set / burning ship fractal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
tumblr post