Created
September 19, 2013 07:36
-
-
Save x-or/6620181 to your computer and use it in GitHub Desktop.
Abstract Choppy Liquid Demo
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
///// Abstract Choppy Liquid | |
///// Copyright (c) 2013, Sergey Shishmintzev | |
///// License: Apache/GPLv3 | |
///// Example: http://youtu.be/ow180xHS3bY | |
///// 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. | |
///// | |
///// 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 | |
#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; | |
} | |
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 particle_t { | |
int i, j; // point coordinates | |
int di, dj; // partcle coordinates increment | |
int ai, aj; // bar angle | |
int bar; // bar length | |
} point_t; | |
int main() { | |
using std::cout; | |
using std::endl; | |
using std::min; | |
using std::max; | |
using std::vector; | |
using std::pair; | |
using std::make_pair; | |
const int modulo = 1920; // arbitrary even number | |
// output image size | |
const int width = 1920; | |
const int height = 1080; | |
const int points_count = 99; // 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 = make_rgb(204, 255, 145); // green | |
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)); | |
} | |
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 rng6(time(0)/6); | |
RNGType rng7(time(0)/7); | |
boost::uniform_int<> i_range( 0, height-1 ); | |
boost::uniform_int<> j_range( 0, width-1 ); | |
boost::uniform_int<> direction_range( 0, 3 ); | |
boost::uniform_int<> value_range( 0, 1 ); | |
boost::uniform_int<> bar_angle_range( 0, 2 ); | |
boost::uniform_int<> bar_length_range( 1, 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_direction_index(rng3, direction_range); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_value_index(rng4, value_range); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_bar_angle(rng5, value_range); | |
boost::variate_generator< RNGType, boost::uniform_int<> > random_bar_length(rng6, bar_length_range); | |
const int direction[] = {-2, 2, -3, 3}; // slow and fast moves | |
const int bar_angle[] = {-1, 1, 0}; | |
int value = 1; // default point weight (from 1 to modulo-1) | |
// generate random positions for points | |
for (int p = 0; p < points_count; p++) { | |
int i, j; | |
i = random_i(); | |
j = random_j(); | |
assert(i >= 0); | |
assert(j >= 0); | |
assert(i < height); | |
assert(j < width); | |
point[p].i = i; | |
point[p].j = j; | |
point[p].di = direction[random_direction_index()]; | |
point[p].dj = direction[random_direction_index()]; | |
point[p].ai = bar_angle[random_bar_angle()]; | |
point[p].aj = bar_angle[random_bar_angle()]; | |
point[p].bar = random_bar_length(); | |
} | |
boost::uniform_01<RNGType> zeroone(rng7); | |
const int frames = 10000; // count of the images to produce | |
matrix<int> state(height, width); | |
matrix<int> future_state(height, width); | |
for (int frame = 0; frame < frames; frame++) { | |
cout << "frame #" << frame << " value=" << value << endl; | |
future_state.clear(); | |
for (int p = 0; p < points_count; p++) { | |
int i, j; | |
int di = point[p].di; | |
int dj = point[p].dj; | |
int bi, bj; | |
while (1) { | |
i = point[p].i + di; | |
j = point[p].j + dj; | |
if ((i >= height) || (i < 0)) { | |
di = direction[random_direction_index()]; | |
continue; | |
} | |
if ((j >= width) || (j < 0)) { | |
dj = direction[random_direction_index()]; | |
continue; | |
} | |
// chaotic moving | |
if (zeroone() < 0.01) | |
di = direction[random_direction_index()]; | |
if (zeroone() < 0.01) | |
dj = direction[random_direction_index()]; | |
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 point position increment | |
point[p].di = di; | |
point[p].dj = dj; | |
// draw a bar | |
for (int x = 0; x < point[p].bar; x++) { | |
bi = i + x*point[p].ai; | |
if ((bi < 0) || (bi >= height)) | |
break; | |
bj = j + x*point[p].aj; | |
if ((bj < 0) || (bj >= width)) | |
break; | |
future_state(bi, bj) = mod(future_state(bi, bj) + value, modulo); | |
} | |
} // 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). | |
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