Created
November 25, 2015 23:09
-
-
Save hexmoire/c832548382e914aee423 to your computer and use it in GitHub Desktop.
drawing triangles between simulated droplets / particles
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
/* | |
* | |
* 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