//this is part of toxi's "box fluid demo" modified for purposes of extracting vertex colors from volumetric color data
void drawFilledMesh() {
noStroke();
int num=mesh.getNumFaces();
mesh.computeVertexNormals();
beginShape(TRIANGLES);
for (int i=0; i<num; i++) {
Face f=mesh.faces.get(i);
fill(colors[(int)map(f.a.x, -scs/2, scs/2, 0, dimX)][(int)map(f.a.y, -scs, scs, 0, dimY)][(int)map(f.a.z, -scs/2, scs/2, 0, dimZ)]);
normal(f.a.normal);
vertex(f.a);
fill(colors[(int)map(f.b.x, -scs/2, scs/2, 0, dimX)][(int)map(f.b.y, -scs, scs, 0, dimY)][(int)map(f.b.z, -scs/2, scs/2, 0, dimZ)]);
normal(f.b.normal);
vertex(f.b);
fill(colors[(int)map(f.c.x, -scs/2, scs/2, 0, dimX)][(int)map(f.c.y, -scs, scs, 0, dimY)][(int)map(f.c.z, -scs/2, scs/2, 0, dimZ)]);
normal(f.c.normal);
vertex(f.c);
}
endShape();
}
void normal(Vec3D v) {
normal(v.x, v.y, v.z);
}
void vertex(Vec3D v) {
vertex(v.x, v.y, v.z);
}
void vertex(Vec3D v, float _u, float _v) {
vertex(v.x, v.y, v.z, _u, _v);
}
//this part comes from "scalar field polygonisation" by Shane Nelson http://www.openprocessing.org/visuals/?visualID=49742
int gridres =dimX, oldgridres = 0;
float[][][] input;
boolean fill = false;
float slabTreshold=0.2;
int floorDensity=8;
int slabColor=200;
void drawSlice(float[][][] input, int sliceY) {
stroke(0);
strokeWeight(2);
fill(slabColor);
for (int k=0;k<gridres-1;k++)for (int j=0;j<gridres-1;j++) {
float a = input[sliceY][j][k], b = input[sliceY][j+1][k],
c = input[sliceY][j][k+1], d = input[sliceY][j+1][k+1];
if (a < slabTreshold && b < slabTreshold && c < slabTreshold && d < slabTreshold && fill) continue;
float ab = (slabTreshold - a) / (b - a),
cd = (slabTreshold - c) / (d - c),
ac = (slabTreshold - a) / (c - a),
bd = (slabTreshold - b) / (d - b);
beginShape();
if (a < slabTreshold) vertex(j, 0, k);
if (ab > 0 && ab < 1) vertex(j+ab, 0, k);
if (b < slabTreshold) vertex(j+1, 0, k);
if (bd > 0 && bd < 1) vertex(j+1, 0, k+bd);
if (d < slabTreshold) vertex(j+1, 0, k+1);
if (cd > 0 && cd < 1) vertex(j+cd, 0, k+1);
if (c < slabTreshold) vertex(j, 0, k+1);
if (ac > 0 && ac < 1) vertex(j, 0, k+ac);
endShape(CLOSE);
}
}
//fuzzy logic: http://en.wikipedia.org/wiki/Fuzzy_logic
float fTrp(float value, float x0, float x1,float x2, float x3) {//fuzzy trapezoid function
float result=0;
float x=value;
if(x<=x0)result=0;
else if((x>=x1)&&(x<=x2))result=1;
else if((x>x0)&&(x<x1))result=(x/(x1-x0))-(x0/(x1-x0));
else result=constrain((-x/(x3-x2))+(x3/(x3-x2)),0,1);
return result;
}
float fGrade(float value, float x0, float x1){//fuzzy grade function
float result=0;
float x=value;
if(x<=x0)result=0;
else if(x>=x1)result=1;
else{
result=(x/(x1-x0))-(x0/(x1-x0));
}
return result;
}
//fuzzy logical operators:
float fAND(float A, float B) {
return min(A,B);
}
float fOR(float A, float B) {
return max(A,B);
}
float fNOT(float A) {
return 1.0-A;
}
ControlP5 gui;
ControlWindow controlWindow;
void initGUI() {
gui = new ControlP5(this);
gui.setAutoDraw(false);
controlWindow = gui.addControlWindow("controlP5window", 100, 100, 400, 500);
controlWindow.hideCoordinates();
controlWindow.setBackground(color(40));
Controller mySlider1 = gui.addSlider("rFreq", 0, 20, 15, 20, 200, 10);
Controller mySlider2 = gui.addSlider("gFreq", 0, 20, 15, 40, 200, 10);
Controller mySlider3 = gui.addSlider("bFreq", 0, 20, 15, 60, 200, 10);
Controller mySlider4 = gui.addSlider("rIntens", 0, 255, 20, 100, 200, 10);
Controller mySlider5 = gui.addSlider("gIntens", 0, 255, 20, 120, 200, 10);
Controller mySlider6 = gui.addSlider("bIntens", 0, 255, 20, 140, 200, 10);
Controller mySlider7 = gui.addSlider("bcgColor", 0, 255, 20, 180, 200, 10);
Controller mySlider8 = gui.addSlider("slabTreshold", 0.001, 0.3, 20, 220, 200, 10);
Controller mySlider9 = gui.addSlider("slabColor", 0, 255, 20, 200, 200, 10);
Controller myButton1 = gui.addButton("runReaction", 0, 290, 300, 50, 20);
Controller myButton2 = gui.addButton("updateColor", 0, 260, 20, 80, 20);
Controller myButton3 = gui.addButton("randomFreq", 0, 260, 60, 80, 10);
Controller myButton4 = gui.addButton("randomIntens", 0, 260, 100, 80, 10);
Controller myButton5 = gui.addButton("smoothIt", 0, 290, 180, 50, 50);
Controller myToggle1 = gui.addToggle("colorIndexing", true, 290, 240, 50, 20);
mySlider1.setWindow(controlWindow);
mySlider2.setWindow(controlWindow);
mySlider3.setWindow(controlWindow);
mySlider4.setWindow(controlWindow);
mySlider5.setWindow(controlWindow);
mySlider6.setWindow(controlWindow);
mySlider7.setWindow(controlWindow);
mySlider8.setWindow(controlWindow);
mySlider9.setWindow(controlWindow);
myButton1.setWindow(controlWindow);
myButton2.setWindow(controlWindow);
myButton3.setWindow(controlWindow);
myButton4.setWindow(controlWindow);
myButton5.setWindow(controlWindow);
myToggle1.setWindow(controlWindow);
}
public void runReaction() {
runReaction = !runReaction;
}
public void randomFreq() {
rFreq = (int)random(20);
gFreq = (int)random(20);
bFreq = (int)random(20);
gui.controller("rFreq").setValue(rFreq);
gui.controller("gFreq").setValue(gFreq);
gui.controller("bFreq").setValue(bFreq);
updateColors();
}
public void randomIntens() {
rIntens =(int)random(255);
gIntens =(int)random(255);
bIntens =(int)random(255);
gui.controller("rIntens").setValue(rIntens);
gui.controller("gIntens").setValue(gIntens);
gui.controller("bIntens").setValue(bIntens);
updateColors();
}
public void updateColor() {
updateColors();
}
public void smoothIt() {
new LaplacianSmooth().filter(mesh, 1);
}
void colorIndexing(boolean theFlag) {
uniform=theFlag;
}
// SPECULATIVE APPS 001: 'saturation' by echoechonoisenoise
// architecture uses sketches to communicate and discuss ideas. what happens, when they become operative and anyone can modify them?
// echoechonoisenoise added: fuzzy control, volumeutils mesh, colors and floor slabs :) using Shane Nelson's http://www.openprocessing.org/visuals/?visualID=49742
// 3d Belousov-Zhabotinsky reaction _ Tommaso Casucci
// http://www.openprocessing.org/visuals/?visualID=38850
// expansion of Alasdair Turner code
// http://www.openprocessing.org/visuals/?visualID=1263
import peasy.*;
import toxi.volume.*;
import toxi.geom.*;
import toxi.geom.mesh.*;
import toxi.processing.*;
import controlP5.*;
PeasyCam cam;
ToxiclibsSupport gfx;
int dimX = 35;
int dimY = 70;
int dimZ = 35;
int scs=100;
float ISO_THRESHOLD = 0.5;
Vec3D sc=new Vec3D(1, 2, 1).scaleSelf(scs);
VolumetricSpaceArray volume=new VolumetricSpaceArray(sc, dimX, dimY, dimZ);
IsoSurface surface=new HashIsoSurface(volume);
WETriangleMesh mesh;
int rFreq = 10;
int gFreq = 9;
int bFreq = 9;
int rIntens =255;
int gIntens =255;
int bIntens =255;
int bcgColor=60;
float [][][][] a;
float [][][][] b;
float [][][][] c;
int p = 0, q = 1;
boolean runReaction=false;
boolean uniform=true;
void setup () {
size(900, 900, P3D);
cam = new PeasyCam(this, 250);
cam.rotateX(0.5);
cam.rotateY(0.3);
cam.rotateZ(-0.17);
initGUI();
initMeshColors();
initReaction();
for (int i=0;i<50;i++)updateReaction();
updateMesh();
updateColors();
for (int i=0;i<4;i++)new LaplacianSmooth().filter(mesh, 2);
}
void draw () {
background(bcgColor);
pointLight(255, 255, 255, 300, -400, 300);
pointLight(155, 155, 155, -300, -200, -300);
pointLight(200, 200, 220, 300, 500, -300);
drawFilledMesh();
translate(-scs/2, 0, -scs/2);
for (int y=0;y<dimY;y++) {
pushMatrix();
translate(0, map(y, 0, dimY, -scs, scs), 0);
scale(2.9);//very amateur way... didn't spent time on translating Shane's code into toxi.geom.mesh; although would be great to make further operations with it...
if (y%floorDensity==0&&y!=0&&y!=dimY)drawSlice(input, y);
popMatrix();
}
if (runReaction) {
updateReaction();
updateMesh();
updateColors();
}
}
float[][][][] colorspace;
color[][][] colors;
void initMeshColors() {
colorspace=new float[dimX][dimY][dimZ][3];
colors=new color[dimX][dimY][dimZ];
}
//this part is mostly Alasdair Turner' code made 3d by Tomasso Casucci
void initReaction() {
a = new float [dimX ][ dimY ][ dimZ ][2];
b = new float [dimX ][ dimY ][ dimZ ][2];
c = new float [dimX ][ dimY ][ dimZ ][2];
for (int x = 0; x < dimX; x++) {
for (int y = 0; y < dimY; y++) {
for (int z = 0; z< dimZ; z++) {
a[x][y][z][p] = random (1);
b[x][y][z][p] = random (1);
c[x][y][z][p] = random (1);
}
}
}
}
void updateReaction() {
if (gridres != oldgridres) {
input = new float[dimY][gridres][gridres];
oldgridres = gridres;
}
for (int x = 0; x < dimX; x++) {
for (int y = 0; y < dimY; y++) {
for (int z = 0; z < dimZ; z++) {
float c_a = 0.0;
float c_b = 0.0;
float c_c = 0.0;
for (int i = x - 1; i <= x+1; i++) {
for (int j = y - 1; j <= y+1; j++) {
for (int k = z - 1; k <= z+1; k++) {
c_a += a[(i+dimX )% dimX ][(j+dimY )% dimY ][(k+dimZ )% dimZ ][p];
c_b += b[(i+dimX )% dimX ][(j+dimY )% dimY ][(k+dimZ )% dimZ ][p];
c_c += c[(i+dimX )% dimX ][(j+dimY )% dimY ][(k+dimZ )% dimZ ][p];
}
}
}
c_a /= 27.0;
c_b /= 27.0;
c_c /= 27.0;
// adjust these values to alter behaviour
a[x][y][z][q] = constrain(c_a + c_a * (c_b - c_c), 0, 1);
b[x][y][z][q] = constrain(c_b + c_b * (c_c - c_a), 0, 1);
c[x][y][z][q] = constrain(c_c + c_c * (c_a - c_b), 0, 1);
float vValue=fTrp(map(sin(a[x][y][z][q]*9), -1, 1, 0, 1), -0.5, -0.4, 0.4, 0.5);
volume.setVoxelAt(x, y, z, vValue);
colorspace[x][y][z][0]=a[x][y][z][q];
colorspace[x][y][z][1]=b[x][y][z][q];
colorspace[x][y][z][2]=c[x][y][z][q];
if (y%floorDensity==0)input[y][x][z] = a[x][y][z][q];//update floor slab input array
}
}
}
if (p == 0) {
p = 1;
q = 0;
}
else {
p = 0;
q = 1;
}
}
void updateMesh() {
volume.closeSides();
mesh=new WETriangleMesh("iso");
IsoSurface surface=new HashIsoSurface(volume, 0.333333);
surface.computeSurfaceMesh(mesh, ISO_THRESHOLD);
new LaplacianSmooth().filter(mesh, 1);
}
void updateColors() {
if (uniform) {
for (int x = 0; x < dimX; x++) {
for (int y = 0; y < dimY; y++) {
for (int z = 0; z < dimZ; z++) {
colors[x][y][z]=color(map(sin(colorspace[x][y][z][0]*rFreq), -1, 1, 0, rIntens), map(sin(colorspace[x][y][z][0]*gFreq), -1, 1, 0, gIntens), map(sin(colorspace[x][y][z][0]*bFreq), -1, 1, 0, bIntens));
}
}
}
}
else {
for (int x = 0; x < dimX; x++) {
for (int y = 0; y < dimY; y++) {
for (int z = 0; z < dimZ; z++) {
colors[x][y][z]=color(map(sin(colorspace[x][y][z][0]*rFreq), -1, 1, 0, rIntens), map(sin(colorspace[x][y][z][1]*gFreq), -1, 1, 0, gIntens), map(sin(colorspace[x][y][z][2]*bFreq), -1, 1, 0, bIntens));
}
}
}
}
}
my take on Tommaso's http://www.openprocessing.org/visuals/?visualID=38850 3d BZreaction connected with http://www.openprocessing.org/visuals/?visualID=49742 scalar field polygonisation by Shane Nelson
video: http://vimeo.com/37412149
related blog post: http://echoechonoisenoise.wordpress.com/2012/02/25/speculative-apps/