Skip to content

Instantly share code, notes, and snippets.

@x-or
Created September 21, 2013 10:51
Show Gist options
  • Save x-or/6649439 to your computer and use it in GitHub Desktop.
Save x-or/6649439 to your computer and use it in GitHub Desktop.
Fantastic Plasmoids
///// 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