///// 2.5 d reaction diffusion: custom grey scott as voxel array with agent interaction. iterative, cyclic model – diffusion and agent interaction alternates
MAIN:
import processing.video.*;
import processing.dxf.*;
import processing.opengl.*;
import javax.media.opengl.*;
import peasy.org.apache.commons.math.*;
import peasy.*;
import peasy.org.apache.commons.math.geometry.*;
import toxi.math.conversion.*;
import toxi.geom.*;
import toxi.geom.mesh2d.*;
import toxi.util.datatypes.*;
import toxi.util.events.*;
import toxi.geom.mesh.subdiv.*;
import toxi.geom.mesh.*;
import toxi.util.*;
import toxi.math.noise.*;
import toxi.volume.*;
/**
*/
import toxi.sim.grayscott.*;
import toxi.math.*;
import controlP5.*;
import toxi.color.*;
//——————————Volume Params
int DX=120;
int DY=60;
int DZ=60;
OscillatingVolume volumeU;
OscillatingVolume volumeV;
OscillatingVolume volumeF;
VolumetricBrush brushU;
VolumetricBrush brushV;
VolumetricBrush brushF;
Vec3D SCALE=new Vec3D(DX,DY,DZ);
int sX = int(SCALE.x/DX);
int sY = int(SCALE.y/DY);
int sZ = int(SCALE.z/DZ);
//——————————Agent params
//==============================
//Trail length and agent speed – obviously if you want
//to run the agents for ages, you will need longer trails.
//agent speed also has a significant effect on the root geom.
int trailLength = 200;
float agentSpeed = 1;
//ammount to confine to a particular threshold
float confineToThreshold = 0.1;
//distance between trail points
int dropSpacing = 2;
//number of agents
int numAgents = 50;
//if the agent cant find a value in its neighbouring
//voxels that is less than this number from the ideal
//threshold (by default 0.5) =dead. LOWER=MORE DEATH
float cutoffToDie = 0.2;
//if the agent finds a value in its neighbouring
//voxels that is less than this number from the ideal
//threshold (by defauly 0.5), then it starts counting
//towards branching LOWER=LESS BRANCHING
float cutoffToThinkAboutBranching = 0.0;
//The agent needs to have been within its branching cutoff
//for this number of steps, then it branches.
int numEasyMovesBeforeBranch =10;
//number of new agents to make.
int splitIntoAgents=3;
//==============================
//parameters for how the agents modify the field…
//how big the brush is that modifies the diffusion f parameter.
float fBrushSize = 2;
float rootRadius = 2;
//density value for the agents – a negative
//number removes material from the diffusion,
//numbers closer to 0 mean that more agents need to fly
//down a similar path to actually remove the material …
float density = -0.01;
//controls the strength of the diffusion parameter.
float fDensity = -0.05;
Boolean drawAgents = true;
Boolean saveRaw = false;
MovieMaker mm;
Boolean filming =true;
//——————————Diffusion params
int NUM_ITERATIONS = 10;
ControlP5 controlP5;
ControlWindow controlWindow;
GrayScott gs;
ToneMap toneMap;
PImage img;
float f = 0.0210;
float k = 0.076;
float dU = 0.0976;
float dV = 0.0653;
PeasyCam cam;
World world;
int currentLevel =0;
Boolean building = true;
Boolean firstRun = true;
Boolean cycle = true;
//==============================
//make this false to make the simution
//reset to the ground plane rather than oscillating
Boolean oscillate = false;
//==============================
//sets how long the agents and the diffusion
//run for. REMEMBER cant be larger than the actual field.
int agentSteps = 50;
int numSteps = DZ;
int cycleIterator = numSteps;
void setup() {
size(500,500,OPENGL);
cam = new PeasyCam(this,200);
mm = new MovieMaker(this, width, height, “drawing.mov”,30, MovieMaker.ANIMATION, MovieMaker.HIGH);
gs=new PatternedGrayScott(DX,DY,false);
img=loadImage(“r1_60.png”);
gs.setCoefficients(f,k,dU,dV);
// create a color gradient for 256 values
ColorGradient grad=new ColorGradient();
// NamedColors are preset colors, but any TColor can be added
// see javadocs for list of names:
// http://toxiclibs.org/docs/colorutils/toxi/color/NamedColor.html
grad.addColorAt(0,NamedColor.WHITE);
grad.addColorAt(16,NamedColor.CORNSILK);
grad.addColorAt(128,NamedColor.PINK);
grad.addColorAt(192,NamedColor.PURPLE);
grad.addColorAt(255,NamedColor.BLACK);
// this gradient is used to map simulation values to colors
// the first 2 parameters define the min/max values of the
// input range (Gray-Scott produces values in the interval of 0.0 – 0.5)
// setting the max = 0.33 increases the contrast
toneMap=new ToneMap(0,0.33,grad);
gs.seedImage(img.pixels,img.width,img.height);
//setup voxels
volumeU=new OscillatingVolume(SCALE,DX,DY,DZ);
volumeV=new OscillatingVolume(SCALE,DX,DY,DZ);
volumeF=new OscillatingVolume(SCALE,DX,DY,DZ);
brushU=new RoundBrush(volumeU,5);
brushV=new RoundBrush(volumeV,5);
brushF=new RoundBrush(volumeF,5);
volumeU.setLandscape(gs.u,0);
volumeV.setLandscape(gs.v,0);
setupP5();
world = new World();
}
void draw() {
background(255);
gs.setCoefficients(f,k,dU,dV);
//diffusion cycle
if(building){
if(!firstRun){
//read in the layer
gs.u = volumeU.getLayer(currentLevel);
gs.v = volumeV.getLayer(currentLevel);
}
// update the simulation a few time steps
for(int i=0; i<NUM_ITERATIONS; i++) {
gs.update(1);
}
//if first run then just write the values, otherwise
//modify the values.
if(firstRun){
volumeU.setLandscape(gs.u,currentLevel);
volumeV.setLandscape(gs.v,currentLevel);
}else{
volumeU.modLandscape(gs.u,currentLevel);
volumeV.modLandscape(gs.v,currentLevel);
}
//toggle moving up or down and change the level
if(oscillate){
if(cycle){
currentLevel++;
}else{
currentLevel–;
}
}else{
currentLevel++;
}
cycleIterator–;
//completed the cycle
if(cycleIterator <=0){
building = false;
if(firstRun) {
firstRun = false;
world.spawnOnThreshold();
//world.spawn();
}
cycle = !cycle;
cycleIterator = agentSteps;
if(!oscillate) currentLevel = 0;
}
}else{
//setup the oscilation
world.update();
cycleIterator–;
if(cycleIterator<=0){
building = true;
cycleIterator = numSteps;
}
}
volumeV.drawPoints();
if(filming) mm.addFrame();
}
void keyPressed() {
if(key == ‘r’){
gs.reset();
}
if(key == ‘q’){
GsaveData(volumeV,”G:/ncertainties/export/”+day()+”_”+hour()+”_”+minute()+”_”+second()+”voxels.raw”);
}
if(key == ‘w’){
savePts();
}
if(key == ‘e’){
saveParams();
}
if(key == ‘ ‘){
mm.finish();
}
}
AGENTS:
//simple agent class to interact with the data
//field and other agents
class Agent {
Vec3D loc,vel;
ArrayList<Vec3D> trail;
float gVal;
float dropNum;
boolean alive;
int id;
float aX,aY,aZ,lastBest;
int easy;
Agent(Vec3D _loc, Vec3D _vel,int _id){
loc = _loc;
vel = _vel;
trail = new ArrayList<Vec3D>();
gVal = 255;
dropNum = 0;
alive = true;
id = _id;
easy = 0;
lastBest = 0;
}
void run(){
if (alive){
getGridPos();
pushPullToData(true,0.0005);
branch();
volBrushLocation();
update();
}
render();
}
//————————————————————————————-
//Behaviour for avoiding the worst (highest greyscale value) data within a proximity,
//and interpolating the agent velocity away that point.
//————————————————————————————-
void getGridPos(){
aX = constrain(loc.x,0,SCALE.x);
aX = map(aX,0,SCALE.x,0,DX);
aY = constrain(loc.y,0,SCALE.y);
aY = map(aY,0,SCALE.y,0,DY);
aZ = constrain(loc.z,0,SCALE.z);
aZ = map(aZ,0,SCALE.z,0,DZ);
if(loc.x>SCALE.x || loc.y >SCALE.y || loc.z>SCALE.z)vel.invert();
if(loc.x<0 || loc.y <0 || loc.z<0)vel.invert();
}
void pushPullToData(Boolean push, float t){
float bestThreshDiff = 100;
Vec3D bestThreshPt = new Vec3D(0,0,0);
//iterate over neighbouring grid pos
for (int i =-5;i<=5;i++){
for (int j =-5;j<=5;j++){
for (int k =-5; k<=5;k++){
//if the value of this noisepoints better than the others, then update variables
//scale val by distance
Vec3D vec = new Vec3D(i,j,k);
if((aX+i)>=0&&(aX+i)<DX&&(aY+j)>=0&&(aY+j)<DY&&(aZ+k)>=0&&(aZ+k)<DZ){
float val = volumeV.getVoxelAt(int(aX+i),int(aY+j),int(aZ+k));
if(val>0.0001 &&val<0.01){
float threshVal = abs(val-t);
if(threshVal <bestThreshDiff) bestThreshDiff=threshVal;
//float threshVal = val;
vec.scaleSelf(1-(threshVal*threshVal));
bestThreshPt.addSelf(vec);
}
}
}
}
}
//attract to threshold
vel.interpolateToSelf(bestThreshPt,confineToThreshold);
//SOME VERY TOUCHY VALUES HERE BECAUSE OF THE NOISE
// bestThreshDiff*=10;
//die if in a really crappy possy
if(bestThreshDiff>cutoffToDie)alive=false;
//if in a good posy, see if we should branch
if(bestThreshDiff<cutoffToThinkAboutBranching){
easy++;
}else{
easy = 0;
}
}
Boolean moveToThreshold(int searchRad){
getGridPos();
for (int i =-searchRad;i<=searchRad;i++){
for (int j =-searchRad;j<=searchRad;j++){
for (int k =-searchRad;k<=searchRad;k++){
//if the value of this noisepoints better than the others, then update variables
//scale val by distance
Vec3D vec = new Vec3D(i,j,k);
if((aX+i)>=0&&(aX+i)<DX&&(aY+j)>=0&&(aY+j)<DY&&(aZ+k)>=0&&(aZ+k)<DZ){
float val = volumeV.getVoxelAt(int(aX+i),int(aY+j),int(aZ+k))*200;
if(val>=1&&val<5){
//move
loc.addSelf(vec);
return true;
}
}
}
}
}
//if cant find anything then die
loc = new Vec3D(random(0,DX),random(0,DY),random(10,DZ));
return false;
}
//————————————————————————————-
//creates new agents from this one if the agent path is easy.
//————————————————————————————-
void branch(){
if(easy>numEasyMovesBeforeBranch){
easy =0;
for (int i = 1;i<splitIntoAgents;i++){
Vec3D aVel = vel.copy();
aVel.rotateX(random(-0.5,0.5));
aVel.rotateY(random(-0.5,0.5));
aVel.rotateZ(random(-0.5,0.5));
Agent a = new Agent(loc.copy(),aVel,id);
world.newPop.add(a);
}
alive = false;
}
}
//————————————————————————————-
//function for constraining agent movement to only z if they are moving within
//very dense material
//————————————————————————————-
void volBrushLocation(){
brushF.setSize(fBrushSize);
brushF.drawAtGridPos(aX,aY,aZ,fDensity);
brushV.setSize(rootRadius);
brushV.drawAtGridPos(aX,aY,aZ,(density));
if(aZ-5>0) brushV.drawAtGridPos(aX,aY,aZ-5,-density*2);
// brushV.drawAtGridPos(aX,aY,aZ,density);
}
//————————————————————————————-
//Functions for managing trails and rendering the agent.
//————————————————————————————-
void update(){
//move the agent randomly
vel.normalizeTo(agentSpeed);
//vel.interpolateToSelf(new Vec3D (0,0,1),vert);
loc.addSelf(vel);
//update the trail geom every 5 moves
if(dropNum%dropSpacing==0){
if(trail.size()<trailLength){
trail.add(new Vec3D(loc.x, loc.y, loc.z));
}else{
trail.remove(0);
}
dropNum=0;
}
dropNum++;
}
void render(){
//render agent trail as lines
Vec3D ls = null;
for (Vec3D l : trail){
if(ls !=null){
stroke(0);
line (l.x,l.y,l.z,ls.x,ls.y,ls.z);
}
ls = l;
}
}
}
ARRAY OSCILLATIONS
class OscillatingVolume extends VolumetricSpaceArray {
int z;
public OscillatingVolume (Vec3D _S, int _DX, int _DY,int _DZ) {
super(_S,_DX,_DY,_DZ);
z = 0;
}
//—————————
void setLandscape(float[] d,int l){
for (int i=0;i<resX;i+=1){
for (int j =0;j<resY;j+=1){
//set the voxel to the pixel
setVoxelAt(i,j,l,d[(j*resX)+i]);
}
}
//set the current layer
z = l;
}
void modLandscape(float[] d,int l){
for (int i=0;i<resX;i+=1){
for (int j =0;j<resY;j+=1){
//set the voxel to the pixel
float mod = d[(j*resX)+i];
float existing = getVoxelAt(i,j,l);
mod = (mod+existing)/2;
setVoxelAt(i,j,l,mod);
}
}
z = l;
}
float[] getLayer(int level){
float[] d = new float[resX*resY];
for (int i=0;i<resX;i+=1){
for (int j =0;j<resY;j+=1){
//set the voxel to the pixel
d[(j*resX)+i] = getVoxelAt(i,j,level);
}
}
return d;
}
void drawPoints() {
int r=1;
for(int i=0;i<resX;i+=r) {
for(int j=0;j<resY;j+=r) {
for(int k=0;k<resZ;k+=r) {
float v = getVoxelAt(i,j,k)*2000;
if(v>=1&&v<5){
stroke(100);
point(i,j,k);
}
}
}
}
}
}
VOXEL FUNCTIONS
public void GsaveData(OscillatingVolume v,String fn) {
print(“saving volume data…”);
int c=0;
int tot = v.getData().length;
try {
BufferedOutputStream ds = new BufferedOutputStream(new FileOutputStream(fn));
// ds.writeInt(volumeData.length);
for (float element : v.getData()) {
if(element<0)element=0;
ds.write((int)(element*100));
}
ds.flush();
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
WORLD
//Class to manage addition and subtraction of agents from
//the environment
class World {
ArrayList<Agent> agentPop;
ArrayList<Agent> newPop;
World(){
agentPop = new ArrayList<Agent>();
newPop = new ArrayList<Agent>();
}
//————————–main function
void update(){
newPop = new ArrayList<Agent>();
runAgents();
if(newPop.size()>0) addAgents();
}
void runAgents(){
int c = 0;
for (Agent a :agentPop){
a.run();
}
}
void addAgents(){
agentPop.addAll(newPop);
}
void spawn(){
newPop = new ArrayList();
agentPop = new ArrayList();
for (int i=0;i<numAgents;i++){
// Vec3D loc = new Vec3D(random(0,gW*gRes),random(0,gH*gRes),0);
Vec3D loc = new Vec3D(random(0,DX),random(0,DY),3);
Vec3D vel = new Vec3D(0,0,3);
Agent a = new Agent(loc,vel,i);
agentPop.add(a);
}
}
void spawnOnThreshold(){
newPop = new ArrayList();
agentPop = new ArrayList();
for (int i=0;i<numAgents;i++){
// Vec3D loc = new Vec3D(random(0,gW*gRes),random(0,gH*gRes),0);
Vec3D loc = new Vec3D(random(0,DX),random(0,DY),random(10,DZ));
Vec3D vel = new Vec3D(0,0,3);
Agent a = new Agent(loc.copy(),vel,i);
//try to find a threshold 5 times
boolean searching =true;
int c= 0;
while(searching==true) {
if(a.moveToThreshold(5)){
searching = false;
agentPop.add(a);
}else{
c++;
}
if(c>50) searching = false;
}
}
}
}
DIFFUSION
class PatternedGrayScott extends GrayScott {
public PatternedGrayScott(int w, int h, boolean tiling) {
super(w,h,tiling);
}
public float getFCoeffAt(int x, int y) {
if(x*y*currentLevel<volumeF.getData().length){
return (f+volumeF.getVoxelAt(x,y,currentLevel));
}else{
return f;
}
}
/*
public float getKCoeffAt(int x, int y) {
Vec3D val = new Vec3D(mouseX-x,mouseY-y,0);
float d = val.magnitude();
d = k-(map(d,0,height,0,1))*0.04;
return d;
}
*/
}