Created
September 21, 2013 10:51
-
-
Save x-or/6649439 to your computer and use it in GitHub Desktop.
Fantastic Plasmoids
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
///// Fantastic Plasmoids | |
///// Copyright (c) 2013, Sergey Shishmintzev | |
///// License: Apache/GPLv3 | |
///// Example: http://youtu.be/5dI-HHD9RsI | |
///// Compiler: gcc-4.7.3 (cygwin) | |
///// Compile command: g++ -O3 unkgen.cpp -lpng | |
///// P.S. Sorry for my English. It isn't my mother tongue. | |
#include <assert.h> | |
#include <stdio.h> | |
#include <malloc.h> | |
#include <math.h> | |
#include <png.h> | |
#include <iostream> | |
#include <string> | |
#include <vector> | |
#include <boost/numeric/ublas/matrix.hpp> | |
#include "boost/random.hpp" | |
#include "boost/generator_iterator.hpp" | |
using namespace boost::numeric::ublas; | |
inline int mod(int a, int b) { // http://stackoverflow.com/questions/4003232/how-to-code-a-modulo-operator-in-c-c-obj-c-that-handles-negative-numbers | |
if(b < 0) //you can check for b == 0 separately and do what you want | |
return mod(-a, -b); | |
int ret = a % b; | |
if (ret < 0) | |
ret += b; | |
return ret; | |
} | |
inline int make_rgb(int r, int g, int b) { | |
return (b&0xFF) | ((g&0xFF)<<8) | ((r&0xFF)<<16); | |
} | |
inline int get_r(int rgb) { | |
return (rgb>>16) & 0xFF; | |
} | |
inline int get_g(int rgb) { | |
return (rgb>>8) & 0xFF; | |
} | |
inline int get_b(int rgb) { | |
return (rgb) & 0xFF; | |
} | |
inline void setRGB(png_byte *ptr, int val) { | |
ptr[0] = get_r(val); | |
ptr[1] = get_g(val); | |
ptr[2] = get_b(val); | |
} | |
int save_matrix(const matrix<int> &m, const std::string &filename, const std::vector<int> &palette) { | |
// http://www.labbookpages.co.uk/software/imgProc/libPNG.html | |
int code = 0; | |
FILE *fp = NULL; | |
png_structp png_ptr = NULL; | |
png_infop info_ptr = NULL; | |
png_bytep row = NULL; | |
// Open file for writing (binary mode) | |
fp = fopen(filename.c_str(), "wb"); | |
if (fp == NULL) { | |
fprintf(stderr, "Could not open file %s for writing\n", filename.c_str()); | |
code = 1; | |
goto finalise; | |
} | |
// Initialize write structure | |
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); | |
if (png_ptr == NULL) { | |
fprintf(stderr, "Could not allocate write struct\n"); | |
code = 1; | |
goto finalise; | |
} | |
// Initialize info structure | |
info_ptr = png_create_info_struct(png_ptr); | |
if (info_ptr == NULL) { | |
fprintf(stderr, "Could not allocate info struct\n"); | |
code = 1; | |
goto finalise; | |
} | |
// Setup Exception handling | |
if (setjmp(png_jmpbuf(png_ptr))) { | |
fprintf(stderr, "Error during png creation\n"); | |
code = 1; | |
goto finalise; | |
} | |
png_init_io(png_ptr, fp); | |
// Write header (8 bit colour depth) | |
png_set_IHDR(png_ptr, info_ptr, m.size2(), m.size1(), | |
8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, | |
PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); | |
png_write_info(png_ptr, info_ptr); | |
// Allocate memory for one row (3 bytes per pixel - RGB) | |
row = (png_bytep) malloc(3 * m.size2() * sizeof(png_byte)); | |
// Write image data | |
unsigned int i, j; | |
for (i = 0; i < m.size1(); i++) { | |
for (j = 0; j < m.size2(); j++) { | |
setRGB(&(row[j*3]), palette[m(i, j)]); | |
} | |
png_write_row(png_ptr, row); | |
} | |
// End write | |
png_write_end(png_ptr, NULL); | |
finalise: | |
if (fp != NULL) fclose(fp); | |
if (info_ptr != NULL) png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); | |
if (png_ptr != NULL) png_destroy_write_struct(&png_ptr, (png_infopp)NULL); | |
if (row != NULL) free(row); | |
return code; | |
} | |
// Gradient palette routines | |
// Translated from http://jtauber.com/blog/2008/05/18/creating_gradients_programmatically_in_python/ | |
int linear_gradient(int start_value, int stop_value, float start_offset, float stop_offset, float offset) { | |
return floor(start_value + ((offset - start_offset) / (stop_offset - start_offset) * (stop_value - start_value))); | |
} | |
int gradient(int start, int end, float initial_offset, float stop_offset, float offset) { | |
// make gradient color | |
int r = linear_gradient(get_r(start), get_r(end), initial_offset, stop_offset, offset); | |
int g = linear_gradient(get_g(start), get_g(end), initial_offset, stop_offset, offset); | |
int b = linear_gradient(get_b(start), get_b(end), initial_offset, stop_offset, offset); | |
return make_rgb(r, g, b); | |
} | |
typedef struct point_t { | |
int value; // weight | |
int i, j; // coordinates | |
int di, dj; // coordinate increment | |
int radius; // circle radius | |
int max_radius; // avoid circle crossing over zero border, avoid glitches | |
int dr; // radius increment | |
} point_t; | |
int main() { | |
using std::cout; | |
using std::endl; | |
using std::min; | |
using std::max; | |
using std::sin; | |
using std::cos; | |
using std::vector; | |
const int modulo = 1920; // arbitrary even number | |
// output image size | |
const int width = 1920; | |
const int height = 1080; | |
const int points_count = 24; // visual granularity | |
cout << "modulo = " << modulo << endl; | |
cout << "size = " << width << "x" << height << endl; | |
cout << "points count = " << points_count << endl; | |
// make double gradient palette | |
vector<int> palette(modulo); | |
//const int start_color1 = make_rgb(171, 214, 121); // green | |
const int start_color1 = make_rgb(204, 255, 145); // green | |
const int stop_color1 = make_rgb(65, 66, 225); // blue | |
//const int start_color2 = make_rgb(171, 214, 121); // green | |
const int start_color2 = start_color1; | |
const int stop_color2 = make_rgb(209, 35, 42); // red | |
for(int i = 0; i < modulo/2; i++) { | |
palette[i] = gradient(start_color1, stop_color1, 0.0, 1.0, min(i, modulo/2-1)*1.0/(modulo/2-1)); | |
palette[modulo-i-1] = gradient(start_color2, stop_color2, 0.0, 1.0, min(i, modulo/2-1)*1.0/(modulo/2-1)); | |
} | |
#if 1 | |
const int intermediate_color = make_rgb(145, 35, 145); // red | |
for(int i = 0; i < modulo/4; i++) { | |
palette[modulo/4+i] = gradient(palette[modulo/4], intermediate_color, 0.0, 1.0, i*1.0/(modulo/4-1)); | |
palette[-modulo/4+modulo-i-1] = gradient(palette[-modulo/4+modulo-1], intermediate_color, 0.0, 1.0, i*1.0/(modulo/4-1)); | |
} | |
#endif | |
palette[0] = make_rgb(get_r(start_color1)-5, get_g(start_color1)-5, get_b(start_color1)-5); // background color | |
vector<point_t> point(points_count); | |
// random stuff | |
typedef boost::mt19937 RNGType; | |
RNGType rng1(time(0)); | |
RNGType rng2(time(0)/2); | |
RNGType rng3(time(0)/3); | |
RNGType rng4(time(0)/4); | |
RNGType rng5(time(0)/5); | |
RNGType rng01(time(0)/1000); | |
boost::uniform_int<> i_range( 0, height-1 ); | |
boost::uniform_int<> j_range( 0, width-1 ); | |
boost::uniform_int<> step_range( 0, 4 ); | |
boost::uniform_int<> value_range( 1, 25 ); | |
boost::uniform_int<> radius_range( 100, 200 ); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_i(rng1, i_range); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_j(rng2, j_range); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_step_index(rng3, step_range); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_value(rng4, value_range); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_radius(rng5, radius_range); | |
boost::uniform_01<RNGType> zeroone(rng01); | |
const int step[] = {-3, 3, -4, 4, 0}; // slow and fast motion | |
// generate random positions for points | |
for (int p = 0; p < points_count; p++) { | |
point[p].radius = random_radius(); | |
point[p].max_radius = point[p].radius; | |
int i, j; | |
while (1) { // failback loop | |
// avoid circle crossing over zero border, avoid glitches | |
i = random_i() + point[p].radius; | |
if (i >= height) | |
continue; | |
j = random_j() + point[p].radius; | |
if (j >= width) | |
continue; | |
break; | |
} | |
assert(i >= point[p].radius); | |
assert(j >= point[p].radius); | |
assert(i < height); | |
assert(j < width); | |
point[p].i = i; | |
point[p].j = j; | |
point[p].di = step[random_step_index()]; | |
point[p].dj = step[random_step_index()]; | |
point[p].value = 7*point[p].radius/100; | |
if (zeroone() < 0.5) | |
point[p].value = mod(-point[p].value, modulo); | |
} | |
// live circles | |
const int min_live_radius = 100; | |
const int max_live_radius = 200; | |
point[0].value = 15; | |
point[1].value = mod(-point[0].value, modulo); | |
point[1].radius = 150; | |
point[1].radius = point[0].radius; | |
point[0].dr = -5; | |
point[1].dr = 5; | |
const int live_circles_count = 2; | |
for (int p = 0; p < live_circles_count; p++) | |
point[p].max_radius = max_live_radius; | |
const int frames = 10000; // count of the images to produce | |
const int circle_points_count = 256; | |
const int circle_points_quater = circle_points_count/4; | |
assert(circle_points_quater*4 == circle_points_count); | |
matrix<int> state(height, width); | |
matrix<int> future_state(height, width); | |
for (int frame = 0; frame < frames; frame++) { | |
cout << "frame #" << frame << endl; | |
for (int p = 0; p < live_circles_count; p++) { | |
point[p].radius += point[p].dr; | |
if (point[p].radius <= min_live_radius || point[p].radius >= max_live_radius) | |
point[p].dr = -point[p].dr; | |
} | |
future_state.clear(); | |
for (int p = 0; p < points_count; p++) { | |
int i, j; | |
int di = point[p].di; | |
int dj = point[p].dj; | |
while (1) { // failback loop | |
i = point[p].i + di; | |
j = point[p].j + dj; | |
// edge detection | |
if (i <= point[p].max_radius) { // avoid circle crossing over zero border, avoid glitches | |
di = abs(step[random_step_index()]); | |
continue; | |
} else if (i >= height) { | |
di = -abs(step[random_step_index()]); | |
continue; | |
} | |
if (j <= point[p].max_radius) { | |
dj = abs(step[random_step_index()]); | |
continue; | |
} else if (j >= width) { | |
dj = -abs(step[random_step_index()]); | |
continue; | |
} | |
// chaotic motion | |
if (zeroone() < 0.01) | |
di = step[random_step_index()]; | |
if (zeroone() < 0.01) | |
dj = step[random_step_index()]; | |
if (di == 0 && dj == 0) | |
continue; | |
break; | |
} // retry ... | |
assert(i >= 0); | |
assert(j >= 0); | |
assert(i < height); | |
assert(j < width); | |
// new point position | |
point[p].i = i; | |
point[p].j = j; | |
// new position increment | |
point[p].di = di; | |
point[p].dj = dj; | |
int ci, cj; | |
int last_ci = -1, last_cj = -1; | |
const int radius = point[p].radius; | |
const int value = point[p].value; | |
for (int c = 0; c < circle_points_count+1; c++) { | |
ci = i+trunc(radius*cos(2*M_PI*c/circle_points_count)); | |
if (ci < 0 || ci >= height) | |
continue; | |
// draw a circle-like shape, avoid glitches | |
if (c >= 0 && c < (circle_points_quater)) { | |
cj = j + trunc(radius*sin(2*M_PI*c/circle_points_count)); | |
if ((cj == last_cj && ci == last_ci) || cj < 0 || cj >= width) | |
continue; | |
future_state(ci, cj) = mod(future_state(ci, cj) + value, modulo); | |
} else if (c >= (circle_points_quater) && c < (2*circle_points_quater)) { | |
cj = j + trunc(radius*sin(2*M_PI*(2*circle_points_quater-c-1)/circle_points_count)); | |
if ((cj == last_cj && ci == last_ci) || cj < 0 || cj >= width) | |
continue; | |
future_state(ci, cj) = mod(future_state(ci, cj) - value, modulo); | |
} else if (c > 2*circle_points_quater && c <= (3*circle_points_quater)) { | |
cj = j + trunc(radius*sin(2*M_PI*c/circle_points_count)); | |
if ((cj == last_cj && ci == last_ci) || cj < 0 || cj >= width) | |
continue; | |
future_state(ci, cj) = mod(future_state(ci, cj) + value, modulo); | |
} else if (c >= (3*circle_points_quater) && c <= (4*circle_points_quater)) { | |
cj = j + trunc(radius*sin(2*M_PI*(2*circle_points_quater-c+1)/circle_points_count)); | |
if ((cj == last_cj && ci == last_ci) || cj < 0 || cj >= width) | |
continue; | |
future_state(ci, cj) = mod(future_state(ci, cj) - value, modulo); | |
} | |
last_ci = ci; | |
last_cj = cj; | |
} | |
} // p = ... | |
state.clear(); | |
// Backwards an automata rule: future_state(i0, j0) == state(i0, j0) - state(i0, j1) - state(i1, j0) + state(i1, j1) (mod modulo). | |
// It is the rule 15 [Khan1997] when modulo == 2. | |
// It is the unambiguous when we know initial values of the state (state(0, k) == 0 and state(k, 0) == 0). | |
// Reference: | |
// [Khan1997] A.R. Khan, et.al., "VLSI architecture of a cellular automata machine", 1997, | |
// http://dx.doi.org/10.1016/S0898-1221(97)00021-7 | |
unsigned int i0, i1, j0, j1, solved_state; | |
for (i0 = 0; i0 < state.size1()-1; i0++) { | |
i1 = i0 + 1; | |
for (j0 = 0; j0 < state.size2()-1; j0++) { | |
j1 = j0 + 1; | |
solved_state = mod(-state(i0, j0) + state(i0, j1) + state(i1, j0) + future_state(i0, j0), modulo); | |
assert(solved_state >= 0); | |
assert(solved_state < modulo); | |
state(i1, j1) = solved_state; | |
} | |
} | |
// produce image | |
char fn[100]; | |
sprintf(fn, "rand%d-%dx%d-frame%05d.png", modulo, width, height, frame); | |
save_matrix(state, fn, palette); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment