#include <math.h>
#include <stdio.h>
#include <string.h>
#include <SDL2/SDL.h>
#include "patterns.h"
int width, height, size, menu_size, menu_width;
#define PADDING 20
#define SCALE 2
#define CANVAS_X PADDING + menu_width + menu_size
#define CANVAS_Y PADDING
#define COLOR_BLACK 0x000000
#define COLOR_WHITE 0xFFFFFF
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
typedef enum {
RULE, PATTERN,
} nodeType;
typedef struct node {
nodeType type;
int index;
} Node;
typedef struct rule {
Node quads[4];
Node fallback;
} Rule;
Node selection;
Rule rules[8];
int colors[2] = { COLOR_BLACK, COLOR_WHITE };
int outline = 1;
int quit = 0;
int limit = 1;
void drawSquare(int x, int y, int s, unsigned long long pattern, int outline) {
if (outline) {
SDL_Rect rect;
rect.x = x;
rect.y = y;
// Offset by 1 so outlines overlap
rect.w = s + 1;
rect.h = s + 1;
SDL_RenderDrawRect(renderer, &rect);
}
int px, py;
unsigned long long index;
for (px = x; px < x + s; px++) {
for (py = y; py < y + s; py++) {
index = (((px - CANVAS_X) / SCALE) % 8) * 8 + (((py - CANVAS_Y) / SCALE) % 8);
if ((pattern & (1UL << index)) > 0) {
SDL_RenderDrawPoint(renderer, px, py);
}
}
}
}
void expandRule(int x, int y, int s, Rule* rule);
void expandNode(int x, int y, int s, Node node) {
if (node.type == RULE) {
expandRule(x, y, s, &rules[node.index]);
} else if (node.type == PATTERN) {
drawSquare(x + CANVAS_X, y + CANVAS_Y, s, patterns[node.index], outline);
}
}
void expandRule(int x, int y, int s, Rule* rule) {
int h = s / 2;
if (s > limit) {
expandNode(x, y, h, rule->quads[0]);
expandNode(x + h, y, h, rule->quads[1]);
expandNode(x, y + h, h, rule->quads[2]);
expandNode(x + h, y + h, h, rule->quads[3]);
} else if (rule->fallback.type == PATTERN) {
expandNode(x, y, s, rule->fallback);
}
}
void saveBMP() {
SDL_Surface* surface = SDL_GetWindowSurface(window);
SDL_RenderReadPixels(renderer,
NULL,
SDL_PIXELFORMAT_ARGB8888,
surface->pixels,
surface->pitch);
SDL_SaveBMP(surface, "quad-render.bmp");
SDL_FreeSurface(surface);
}
void drawNumber(int x, int y, int n) {
int h = menu_size / 2;
int o = menu_size / 8;
int pattern = (n >= 4) ? WHITE : BLACK;
if (n & 2) {
y += h;
}
if (n & 1) {
x += h;
}
drawSquare(x + o, y + o, h / 2, pattern, 1);
}
void drawNode(int x, int y, Node node) {
switch (node.type) {
case RULE:
drawSquare(x, y, menu_size, BLACK, 1);
drawNumber(x, y, node.index);
break;
case PATTERN:
drawSquare(x, y, menu_size, patterns[node.index], 1);
break;
default:
break;
}
}
void drawRule(int x, int y, int n) {
drawNumber(x, y, n);
Rule rule = rules[n];
drawNode(x, y + menu_size, rule.fallback);
drawNode(x + 2 * menu_size, y, rule.quads[0]);
drawNode(x + 3 * menu_size, y, rule.quads[1]);
drawNode(x + 2 * menu_size, y + menu_size, rule.quads[2]);
drawNode(x + 3 * menu_size, y + menu_size, rule.quads[3]);
}
void drawMenu() {
int i, j, x, y;
for (i = 0; i < 10; i++) {
x = i * menu_size + PADDING;
for (j = 0; j < 4; j ++) {
y = j * menu_size + PADDING;
drawSquare(x, y, menu_size, patterns[i + 10 * j], 1);
}
}
for (i = 0; i < 8; i++) {
drawRule(PADDING + (i % 2) * 6 * menu_size,
(5 + 3 * (i / 2)) * menu_size + PADDING, i);
}
}
void setColor(int color) {
int r = color >> 16;
int g = (color >> 8) & 255;
int b = color & 255;
SDL_SetRenderDrawColor(renderer, r, g, b, SDL_ALPHA_OPAQUE);
}
void draw() {
setColor(colors[0]);
SDL_RenderClear(renderer);
setColor(colors[1]);
drawMenu();
drawSquare(CANVAS_X, CANVAS_Y, size, BLACK, 1);
expandRule(0, 0, size, &rules[0]);
SDL_RenderPresent(renderer);
}
void reset() {
struct node pat0 = { PATTERN, 0 };
for (int i = 0; i < 8; i++) {
rules[i].quads[0] = pat0;
rules[i].quads[1] = pat0;
rules[i].quads[2] = pat0;
rules[i].quads[3] = pat0;
rules[i].fallback = pat0;
}
}
void handleKey(SDL_Event* event) {
switch (event->key.keysym.sym) {
case SDLK_q:
quit = 1;
break;
case SDLK_r:
saveBMP();
break;
case SDLK_o:
outline = !outline;
draw();
break;
case SDLK_c:
reset();
draw();
break;
case SDLK_i: {
int tmp = colors[0];
colors[0] = colors[1];
colors[1] = tmp;
draw();
break;
}
case SDLK_1: {
if (limit < size / 2) {
limit <<= 1;
draw();
}
break;
}
case SDLK_2: {
if (limit > 1) {
limit >>= 1;
draw();
}
break;
}
}
}
void handleMouse(SDL_Event* event) {
int tx = (event->motion.x - PADDING) / menu_size;
int ty = (event->motion.y - PADDING) / menu_size;
if (tx >= 0 && tx < 10) {
if (ty >= 0 && ty < 4) {
selection.type = PATTERN;
selection.index = tx + (10 * ty);
} else if (ty >= 5 && ty < 16) {
ty -= 5;
int rule = 2 * (ty / 3);
ty %= 3;
if (tx >= 6) {
tx -= 6;
rule += 1;
}
if (tx == 0 && ty == 0) {
selection.type = RULE;
selection.index = rule;
} else if (tx == 0 && ty == 1 && selection.type == PATTERN) {
rules[rule].fallback = selection;
draw();
} else if (tx == 2 && ty == 0) {
rules[rule].quads[0] = selection;
draw();
} else if (tx == 3 && ty == 0) {
rules[rule].quads[1] = selection;
draw();
} else if (tx == 2 && ty == 1) {
rules[rule].quads[2] = selection;
draw();
} else if (tx == 3 && ty == 1) {
rules[rule].quads[3] = selection;
draw();
}
}
}
}
int main(int argc, char* argv[]) {
size = 1024;
if (argc == 2) {
size = atoi(argv[1]);
}
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
printf("error initializing SDL: %s\n", SDL_GetError());
return 1;
}
menu_size = size / 16;
menu_width = menu_size * 10;
width = menu_width + menu_size + size + 2 * PADDING;
height = size + 2 * PADDING;
if (SDL_CreateWindowAndRenderer(width, height, 0, &window, &renderer) != 0) {
printf("Error creating window & renderer: %s\n", SDL_GetError());
return 1;
}
selection.type = PATTERN;
selection.index = 11; // White pattern
reset();
draw();
while (!quit) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
quit = 1;
break;
case SDL_KEYDOWN:
handleKey(&event);
break;
case SDL_MOUSEBUTTONDOWN:
handleMouse(&event);
break;
case SDL_WINDOWEVENT:
if (event.window.event == SDL_WINDOWEVENT_EXPOSED) {
draw();
}
}
}
// Render at 60 fps
SDL_Delay(1000 / 60);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}