Skip to content

Instantly share code, notes, and snippets.

@hexmoire
Created November 25, 2015 23:09
Show Gist options
  • Save hexmoire/c832548382e914aee423 to your computer and use it in GitHub Desktop.
Save hexmoire/c832548382e914aee423 to your computer and use it in GitHub Desktop.
drawing triangles between simulated droplets / particles
/*
*
* infrastice 1
*
* coded in Processing 3.0 IDE
* i appreciate a mention/link back in derivative works
*
* hexmoire / michael mcknight
* http://hexmoire.tumblr.com/
*
* this is cleaned up code, not a representation of the mess i make while developing an idea
* the original code generated the gif at:
* http://hexmoire.tumblr.com/post/132806711826/infrastice-1
*
*/
ArrayList<Droplet> droplets;
float vHeight;
int dropletsPerFrame;
float dropletMinR;
float dropletMaxR;
float frameCap;
float spawnCap;
int exportStart;
boolean exporting;
float spawnInner;
float spawnOuter;
float deSpawn;
float reSeedFrame;
void setup() {
//set size for tumblr gif, go 3D.
size (540, 540, P3D);
//make a variable representing a virtual screen height
//which is used to scale geometry to match actual screen height.
vHeight=100;
//set up camera.
//viewing angle.
float cameraA=PI/5;
//z coordinate, a throwback from previous code
//used to keep the z=0 plane at the same scale regardless of angle.
float cameraZ=1.5*(height/2.0) / tan(PI*30.0 / 180.0);
perspective(cameraA, 1, cameraZ/10, cameraZ*10);
camera(0, 0, cameraZ, 50, 0, 0, 0, -1, 0);
//instantiate ArrayList of Droplet, which will be the invisible core of the sketch.
droplets = new ArrayList<Droplet>();
//edit these initial values to change Droplet behavior.
dropletsPerFrame=1;
dropletMinR=.3;
dropletMaxR=1;
//the final frame - increasing this makes the sketch generate *more* frames before it loops.
//decreasing it reduces the number of frames before loop.
//making the sketch run for a while before exporting allows the droplets state to prewarm
//and get to the point where the state repeats.
frameCap=500;
//frame at which droplets stop spawning.
spawnCap=frameCap+1;
//the random number generator reseeds when
//((frameCount-1)%reSeedFrame == 0).
//so, reSeedFrame determines how many frames are in a loop.
reSeedFrame=50;
//the frame at which exporting begins.
//calculated to make the animation loop work.
exportStart=(int)(frameCap-reSeedFrame);
//setting exporting to true will export a gif of the frames
//GT exportStart and LTE frameCap.
exporting=false;
//set parameters for spawn zone and despawn boundary,
//used by spawnPos() and outOfBounds().
spawnInner=1.4;
spawnOuter=1.7;
deSpawn=1.7;
//get ready to draw white triangles.
noStroke();
fill(255);
}
//this is a long function, so get ready for a slog
void draw() {
//default lighting.
lights();
//reseed random number generator when the current frame is the start of the loop.
if ( (frameCount-1)%reSeedFrame==0) {
randomSeed(0);
println(frameCount);
}
//make the sketch scale with screen height and compose the scene a little.
scale(height/vHeight);
scale(1.5);
//purple.
background(40,0,50);
//spawn Droplet(s).
if (frameCount<spawnCap) {
for (int k=0; k<dropletsPerFrame; k++) {
PVector c;
PVector p=spawnPos();
PVector v;
float r,g,b;
if (random(1)<.5) {
r=255;g=50;b=255;
} else {
r=255;g=150;b=255;
}
c=new PVector(r,g,b);
p=new PVector(p.x, abs(p.y), p.z);
v=new PVector(0, 0, 0);
droplets.add(new Droplet( p, random(dropletMinR, dropletMaxR), v, c));
droplets.get(droplets.size()-1).col=c;
}
}
//physics for droplets.
//loop through each droplet, starting at the largest index
//to make removing them easier.
for (int i=droplets.size()-1; i>=0; i--) {
Droplet d=droplets.get(i);
//apply some still fluid resistance, which does
//*conceptually* conflict with the subsequent
//moving fluid resistance.
d.setV(d.v*.98);
//create a vector representing some kind of fluid flow
//around the droplet.
PVector wind = new PVector(d.pos.x, d.pos.y, d.pos.z);
//make the fluid move slightly negative of the droplet's
//position, pulling it toward (0,0,0).
wind.mult(-.025);
//do a little vector and rotation matrix math
//to add a YZ twist to the fluid.
float r=d.pos.mag();
float alpha=TWO_PI*.002*(1-r/100);
float twisty=d.pos.y*cos(alpha) + d.pos.z*sin(alpha);
float twistz=d.pos.y*-sin(alpha) + d.pos.z*cos(alpha);
PVector twist = new PVector(0, twisty-d.pos.y, twistz-d.pos.z);
wind.add(twist);
//do it again, but on the XY this time.
alpha=TWO_PI*.005*(1-r/100);
float twistx=d.pos.x*cos(alpha) + d.pos.y*sin(alpha);
twisty=d.pos.x*-sin(alpha) + d.pos.y*cos(alpha);
twist = new PVector(twistx-d.pos.x, twisty-d.pos.y, 0);
wind.add(twist);
//simulate the pull of a moving fluid.
//change the Droplet velocity.
d.wind(wind);
//move the droplet.
d.pos.add(d.vel);
//if the droplet is close to center or out of bounds,
//remove it.
if (d.r<100./540*.1 || outOfBounds(d)) {
droplets.remove(i);
}
}
//iterate through every pair of droplets to see if they touch.
//if they touch, combine them.
for (int i=droplets.size()-1; i>=0; i--) {
for (int j=i-1; j>=0; j--) {
Droplet di=droplets.get(i);
Droplet dj=droplets.get(j);
if (dj.intersect(di)) {
dj.absorb(di);
//remove the absorbed droplet.
droplets.remove(i);
//manipulate indices so that the inner loop
//will begin iterating on the next i index.
i--;
j=i;
}
}
}
//instead of drawing the droplets, draw triangles between
//groups of three that are suitable distances apart.
infrastice(droplets);
//export frames.
if (exporting && frameCount>exportStart) {
saveFrame("frames/f"+nf(frameCount, 3)+".gif");
}
//exit if at frameCap.
if (frameCount==frameCap) {
println("exiting");
exit();
}
}
//draw triangles between droplets
void infrastice(ArrayList<Droplet> droplets){
//define minimum and maximum edge lengths.
float minEdge=0;
float maxEdge=10;
//start a shape so that vertex() can be called to draw triangles.
beginShape(TRIANGLES);
//start iterating through all possible groups of three,
//checking the positions of each pair within the group
//for appropriate edge length.
for (int i=droplets.size()-1; i>=0; i--){
for (int j=i-1; j>=0; j--){
PVector ip = droplets.get(i).pos;
PVector jp = droplets.get(j).pos;
if(jp.dist(ip)>minEdge && jp.dist(ip)<maxEdge){
for(int k=j-1; k>=0; k--){
PVector kp = droplets.get(k).pos;
if(jp.dist(kp)>minEdge && ip.dist(kp)>minEdge &&
jp.dist(kp)<maxEdge && ip.dist(kp)<maxEdge){
//ah, finally, a triangle that can be drawn.
vertex(ip.x,ip.y,ip.z);
vertex(jp.x,jp.y,jp.z);
vertex(kp.x,kp.y,kp.z);
}
}
}
}
}
endShape();
}
//the droplet itself.
class Droplet {
protected PVector pos;
protected PVector vel;
protected PVector col;
protected float v;
protected float r;
//constructor - maybe it would be better to set volume than radius, depending on the application.
Droplet(PVector _pos, float _r) {
pos = _pos;
vel = new PVector(0, 0, 0);
col = new PVector(255, 255, 255);
setR(_r);
}
//constructor with velocity and color
Droplet(PVector _pos, float _r, PVector _vel, PVector _col) {
pos = _pos;
vel = _vel;
col = _col;
setR(_r);
}
//set radius and update volume.
void setR(float _r) {
r=_r;
v=4./3*PI*pow(r, 3);
}
//set volume and update radius.
void setV(float _v) {
v=_v;
r=pow(v/(4./3*PI), 1./3);
}
//return color as an int
int getC() {
return color (col.x, col.y, col.z);
}
//check for intersection, return boolean.
public boolean intersect(Droplet d) {
return ( pos.dist(d.pos) < r+d.r );
}
//absorb a droplet
public void absorb(Droplet d) {
//use PVector lerp to move surviving drop to center of mass.
pos.lerp ( d.pos, d.v / (v+d.v) );
vel.lerp ( d.vel, d.v / (v+d.v) );
//lerp the colors together as well
col.lerp ( d.col, d.v / (v+d.v) );
setV ( v+d.v );
}
//apply wind.
public void wind(PVector w) {
//find wind velocity relative to droplet velocity.
PVector v1=PVector.sub(w,vel);
//modify by wind resistance.
v1=v1.mult(calcResistance());
//again, probably nonsense that works visually
//volume / area = r / 3
PVector f=v1.mult(r/3);
//accelerate the droplet
//acceleration = force / mass
vel.add(f.mult(1/v));
}
//calculate wind resistance based on radius.
//this is probably nonsense, but it looks nice.
float calcResistance() {
return 1-1/(r+1);
}
}
//return a PVector between the specified radii
//this is not an even distribution of points
PVector spawnPos() {
float alpha=random(TWO_PI);
float beta=random(TWO_PI);
float r=random(spawnInner, spawnOuter);
return new PVector( cos(beta)*sin(alpha)*r*vHeight/2, cos(beta)*cos(alpha)*r*vHeight/2, sin(beta)*r*vHeight/2 );
}
//return true if a droplet is out of bounds, otherwise false
boolean outOfBounds(Droplet d) {
if (d.pos.mag() > vHeight/2*deSpawn) return true;
return false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment