-
-
Save anonymous/11ec707e5706620ac95c to your computer and use it in GitHub Desktop.
sawtooth
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
//Processing source for sawtooth | |
//@author amiagoodperson.tumblr.com | |
//technically two separate files but it doesn't really matter | |
//specifics.pde | |
//--Parameters-- | |
color bg = color(0, 0, 0); //rgb | |
int tIncr = 9; //how much tmpCount increments by | |
String filename = "sawtooth"; //leave blank to make no images | |
int saveFreq = 4; | |
int backgroundFreq = 0; // every n frames, 0 for always, -1 for never | |
//possibly: layer things? | |
//todo: | |
/* done: | |
* "radial": drawn at radius r from centre | |
* "radialr": "radial reverse", starts from outside in | |
* "radialf": "full radial", a full line instead of a ray | |
* "grid": drawn in a grid, specified further by grid | |
* "recursive": grid, but done recursively (yields different results with tmpCount) | |
*/ | |
String scheme = "radial"; | |
//done: "xy" (regular 2-d), "hex" (hex grid) | |
String grid = "hex"; | |
int radialnum = 16; // # of times to rotate on radial scheme | |
float spacing = 5; | |
//planned: transformations such as: zooming perspective | |
//--Phase-- | |
//"sine": sine wave, "sawtooth": sawtooth wave / mod, "ramptooth": line + sawtooth | |
/* done: | |
* scale is constant multiplier | |
* if mod is non-zero, c %= mod | |
* "linear": scale*count = c | |
* "triangle": c, bounded to between 0 and p[0] in a triangle wave | |
* "parabolic": between -p[0] and p[0], but scales quadratically | |
* "parabroke": parabolic, but shifted outside [0, p[0]] (uses p[0]^2 / 2 - (thing) instead of -p[0] + (thing) ) | |
* "trapezoid": same as triangle, but spends p[1] time at max / min values | |
* "noise": built-in noise function at c * p[0] | |
*/ | |
//TODO: only save one full cycle of images | |
String pFunc = "triangle"; | |
float scale = PI/30; | |
float mod = 0; | |
float[] p = {2*PI, PI/4}; | |
float phase(int count) | |
{ | |
float c = scale*count; | |
if(mod > 0 && !pFunc.equals("trapezoid")) | |
c %= mod; | |
if(pFunc.equals("linear")) | |
return c; | |
else if(pFunc.equals("triangle")) | |
return abs(p[0] - (c % (2*p[0]))); | |
else if(pFunc.equals("trapezoid")) | |
return constrain(abs(p[0] + p[1] - (c % (2*(p[0]+p[1])))) - p[1]/2, 0, p[0]); | |
else if(pFunc.equals("noise")) | |
return p[0] * noise(c/5); | |
else if(pFunc.equals("parabroke")) | |
return (sq(p[0])/2 - sq(p[0] - (c % (2*p[0])))) / p[0]; | |
else if(pFunc.equals("parabolic")) | |
return (sq(p[0]) - sq(p[0] - (c % (2*p[0])))) / p[0]; | |
return c; | |
} | |
//--Drawing-- | |
String mode = "circlewaves"; | |
int lastbg = 0; | |
float rand = random(1); | |
boolean toggle = false; | |
//phase has the current phase corresponding to count | |
//tphase has the phase corrsponding to tmpCount | |
void drawSomething(float x, float y) | |
{ | |
toggle = !toggle; | |
float tphase = phase(tmpCount); | |
if(mode.equals("googly")) | |
drawGooglyEyes(x, y, tphase); | |
else if(mode.equals("twirlers")) | |
drawTwirlers(x, y, tphase); | |
else if(mode.equals("circlewaves")) | |
drawCircleWaves(x, y, tphase); | |
else if(mode.equals("noise")) | |
drawNoise(x, y, tphase); | |
else if(mode.equals("ocircles")) | |
drawOffsetCircles(x, y, tphase); | |
else if(mode.equals("bcurves")) | |
drawBezierCurves(x, y, tphase); | |
else if(mode.equals("signalavg")) | |
drawSignalAverage(x, y, tphase); | |
else if(mode.equals("deriv")) | |
drawDerivatives(x, y, tphase); | |
} | |
//function and derivative helpers | |
float f(float x) | |
{ | |
//to define a periodic function with a specified period | |
float period = 2*PI; | |
if(period != 0) | |
{ | |
x %= period; | |
if(x < 0) | |
x -= period*floor(x/period); | |
} | |
return sin(x*1.8); | |
} | |
float dnf(float x, int n) | |
{ | |
float incr = 0.1; | |
if(n == 1) | |
return (f(x+incr) - f(x-incr)) / (2*incr); | |
return (dnf(x+incr, n-1) - dnf(x-incr, n-1)) / (2*incr); | |
} | |
void drawDerivatives(float x, float y, float tphase) | |
{ | |
float amp1 = 10; | |
float amp2 = 10; | |
float radius = max(5, spacing*0.75); | |
float input = x*0.01 + tphase-p[0]/2; | |
fill((rand*255 + 80*tphase/p[0]) % 255, 230, 230); | |
ellipse(x, y - amp1*f(input), radius, radius); | |
for(int i = 1; i < 5; i++) | |
{ | |
//amp2 = 50; | |
fill((40*i + rand*255 + 80*tphase/p[0]) % 255, 230, 230); | |
ellipse(x, y - amp2*dnf(input, i), radius, radius); | |
} | |
} | |
void drawSignalAverage(float x, float y, float tphase) | |
{ | |
//wave velocity + amplitudes | |
float v1 = 4;// * 1/(1 + tphase/p[0]); | |
float v2 = 10;// * (1 + tphase/p[0]); | |
float a1 = 120; | |
float a2 = 70;//*(x/(w/2));//*sin(PI/w*(x-w/2)); | |
float radius = max(5, spacing*0.75); | |
//"angular velocity", phase dependence on time | |
float w1 = -1*w/2*tphase/p[0]; | |
float w2 = 1*w/2*tphase/p[0]; | |
//these phases are only dependent on one coord to achieve the desired effect. | |
float tp1 = (x - w1) * 0.01 * v1; | |
float tp2 = (x - w2) * 0.01 * v2; | |
fill((rand*255 + 80*tphase/p[0]) % 255, 230, 230); | |
ellipse(x, y + sin(tp1)*a1*(tphase/p[0]) + sin(tp2)*a2*(1 - tphase/p[0]), radius, radius); | |
//ellipse(x, y + 0.5*sin(tp1)*a1*(tphase/p[0]) + noise(1, tp2*0.01)*sin(tp2)*a2*(1 - tphase/p[0]), radius, radius); | |
} | |
void drawBezierCurves(float x, float y, float tphase) | |
{ | |
boolean stroke = true; | |
int fillAlpha = 30; | |
boolean bnw = false; | |
int mode = 3; //anchor systems | |
if(fillAlpha > 0) | |
fill((rand*255 + sin(tphase)*40) % 255, 200, 200, fillAlpha); | |
else | |
noFill(); | |
if(stroke) | |
{ | |
strokeWeight(1); | |
if(bnw) | |
stroke(255); | |
else | |
stroke((rand*255 + sin(tphase)*40) % 255, 200, 200); | |
} | |
else | |
noStroke(); | |
//radial / radialr | |
if(y == 0) | |
{ | |
PVector anchor1, anchor2, anchor3, anchor4; | |
anchor1 = new PVector(x, -h/2); | |
anchor2 = new PVector(x, h/2); | |
switch(mode) | |
{ | |
default: | |
case 1: | |
anchor3 = new PVector(x + 0*w*sin(tphase), y + 0*h/4); | |
anchor4 = new PVector(x - 0.2*w*sin(tphase), y - h/4); | |
break; | |
case 2: | |
anchor3 = new PVector(x + 0.5*w, y + h/2*sin(tphase)); | |
anchor4 = new PVector(x - 0.5*w, y - h/2*sin(tphase)); | |
break; | |
case 3: | |
anchor3 = new PVector(x + w/2*cos(tphase), y + h/2*sin(tphase)); | |
anchor4 = new PVector(x - w/2*cos(tphase), y - h/2*sin(tphase)); | |
break; | |
} | |
bezier(anchor1.x, anchor1.y, anchor3.x, anchor3.y, anchor4.x, anchor4.y, anchor2.x, anchor2.y); | |
} | |
} | |
//pretty much only looks good on radialr / radialnum=1 | |
void drawOffsetCircles(float x, float y, float tphase) | |
{ | |
boolean bnw = false; | |
boolean phaseColor = true; | |
boolean fixedOffDir = false; | |
PVector v = new PVector(x, y); | |
PVector v2 = new PVector(x, y); | |
v2.setMag(spacing); | |
if(fixedOffDir) | |
{ | |
v2.rotate(2*PI*rand); | |
v2.mult(tphase); | |
} | |
else | |
{ | |
v2.rotate(tphase); | |
v.mult(sqrt(0.5)); | |
} | |
if(bnw) | |
{ | |
if(toggle) | |
fill(0, 0, 0); | |
else | |
fill(0, 0, 255); | |
} | |
else | |
{ | |
if(phaseColor) | |
fill((255*rand + map(sin(tphase), -1, 1, 0, 60) + 255) % 255, 200, 200); | |
else | |
{ | |
if(toggle) | |
fill(rand*255, 200, 240); | |
else | |
fill((rand*255 + rand*991) % 255, 200, 200); | |
} | |
} | |
ellipse(v2.x, v2.y, v.mag(), v.mag()); | |
} | |
//just a working name, doesn't actually use noise all that much | |
void drawNoise(float x, float y, float tphase) | |
{ | |
boolean rectangle = false; | |
boolean phaseColor = true; | |
float threshold = 0.35; //returns if noise < threshold | |
PVector v = new PVector(x, y); | |
//v.rotate(radians(45)); | |
//v.rotate(tphase); | |
if(phaseColor) | |
fill((255*rand + map(tphase, 0, p[0], 0, 40) + 255) % 255, 240, 240); | |
else | |
fill((255*(rand + noise(x*0.005, y*0.005, tphase))) % 255, 240, 240); | |
if(noise(v.x, v.y, tphase*0.05) < threshold) | |
return; | |
if(rectangle) | |
rect(v.x-spacing/2, v.y-spacing/2, spacing, spacing); | |
else | |
ellipse(v.x, v.y, spacing/2, spacing/2); | |
} | |
void drawCircleWaves(float x, float y, float tphase) | |
{ | |
//either multiply v or add another sin/cos thing on to the coords | |
boolean multiplyv = true; //true: rotate / no rotate | |
boolean rotate = true; | |
//in order of priority | |
boolean lowColor = false; | |
boolean bnw = false; | |
boolean distfill = true; | |
PVector v = new PVector(x, y); | |
float r = spacing/2; | |
if(bnw) | |
fill(0); | |
else if(lowColor) | |
{ | |
if(tphase <= p[0]/2) | |
fill(255*rand, 200, 255); | |
else | |
fill((255*rand + 40) % 255, 200, 255); | |
} | |
else if(distfill) | |
fill((255*rand + map(v.mag(), 0, w/sqrt(2), 0, 80)) % 255, 200, 255); | |
else | |
fill((255*rand + map(tphase, 0, p[0], 0, 40)) % 255, 200, 255); | |
if(rotate) | |
v.rotate(tphase/p[0]*50.0/v.mag()); | |
if(multiplyv) | |
{ | |
v.mult(0.9+0.2*tphase/p[0]); | |
ellipse(v.x, v.y, r, r); | |
} | |
else | |
ellipse(v.x+spacing*cos(tphase), v.y+spacing*sin(tphase), r, r); | |
} | |
//draws a circle made out of 'slices' of equal size, each of a different color (in c) | |
//rotates based on phase | |
void drawTwirlers(float x, float y, float tphase) | |
{ | |
//params | |
boolean useFocus = false; | |
float focusRadius = w/4; | |
PVector focus = new PVector(w/4*cos(tphase) - x, h/2*sin(tphase) - y); | |
boolean coloredBG = false; | |
float r = spacing*0.425; | |
int cseed = 140;//(int)(255*noise(-0.2, 0.4)); //constant at run time | |
int cspacing = 30; | |
int ncolors = 3; | |
color[] c = new color[ncolors]; | |
for(int i = 0; i < ncolors; i++) | |
c[i] = color((cseed+i*cspacing) % 255, 200, 250); | |
float ang; | |
fill(c[0]); | |
if(coloredBG && count != lastbg) | |
{ | |
background(cseed, 200, 250); | |
lastbg = count; | |
} | |
else if(!coloredBG) | |
ellipse(x, y, r, r); | |
for(int i = 1; i < ncolors; i++) | |
{ | |
fill(c[i]); | |
if(useFocus) | |
ang = focus.heading() - PI/(ncolors*2) + TAU*i/ncolors; | |
else | |
ang = (tphase+i*TAU/ncolors) % TAU; | |
arc(x, y, r, r, ang, ang+TAU/ncolors, PIE); | |
} | |
} | |
void drawGooglyEyes(float x, float y, float tphase) | |
{ | |
//looking at same point | |
boolean useFocus = true; | |
float focusRadius = w/4; | |
PVector focus = new PVector(w/4*cos(tphase) - x, h/2*sin(tphase) - y); | |
boolean bnw = true; //black and white | |
boolean eyes = true; //white circles | |
//eyeballs | |
float r = spacing/3; | |
if(eyes) | |
{ | |
fill(255); | |
//fill(255.0/(2*PI)*tphase, 200, 200); | |
ellipse(x, y, r, r); | |
} | |
//googly eye interior: | |
//smaller circles, offset colour | |
float r2 = r/2; | |
float dcolor = 40; | |
PVector v2 = new PVector(r2, 0); | |
if(useFocus) | |
v2.rotate(focus.heading()); | |
else | |
v2.rotate(tphase); | |
if(bnw) | |
fill(0); | |
else | |
fill((dcolor + 255.0/(2*PI)*tphase) % 255, 200, 200); | |
ellipse(x+v2.x, y+v2.y, r2, r2); | |
} | |
//framework.pde | |
int w, h; | |
int fps = 60; | |
int count = 0; | |
int tmpCount; | |
float phase; | |
void setup() | |
{ | |
size(500, 500); | |
w = width; | |
h = height; | |
frameRate(fps); | |
background(0); | |
initialize(); | |
} | |
void initialize() | |
{ | |
colorMode(HSB); | |
ellipseMode(RADIUS); | |
fill(255); | |
noStroke(); | |
} | |
void draw() | |
{ | |
if(backgroundFreq == 0 || (backgroundFreq > 0 && count % backgroundFreq == 0)) | |
background(bg); | |
count++; | |
drawMain(); | |
if(filename.length() > 0 && count % saveFreq == 0) | |
save(filename + filename(count/saveFreq) + ".png"); | |
} | |
void drawMain() | |
{ | |
int dist; | |
translate(w/2, h/2); | |
phase = phase(count); | |
tmpCount = count; | |
//print(phase + " " + count + "\t"); | |
//e.g. concentric circles | |
//increments once per circle | |
if(scheme.equals("radial")) | |
drawRadial(); | |
//reverse order | |
else if(scheme.equals("radialr")) | |
drawRadialReverse(); | |
else if(scheme.equals("radialf")) | |
drawRadialFull(); | |
//flood-fill style grid | |
else if(scheme.equals("recursive")) | |
drawRecursive(); | |
//regular grid | |
else if(scheme.equals("grid")) | |
{ | |
if(grid.equals("hex")) | |
drawHexGrid(); | |
else if(grid.equals("xy")) | |
drawGrid(); | |
} | |
} | |
void drawRadial() | |
{ | |
//drawSomething(0, 0); | |
for(int i = 0; i < radialnum; i++) | |
{ | |
tmpCount = count; | |
for(float r = 0; r*r < (w*w + h*h); r += spacing) | |
{ | |
tmpCount += tIncr; | |
drawSomething(r, 0); | |
} | |
rotate(TAU/radialnum); | |
} | |
} | |
void drawRadialFull() | |
{ | |
//drawSomething(0, 0); | |
for(int i = 0; i < radialnum; i++) | |
{ | |
tmpCount = count; | |
for(float r = 0; r*r < (w*w + h*h); r += spacing) | |
{ | |
drawSomething(r, 0); | |
tmpCount += tIncr; | |
} | |
tmpCount = count; | |
for(float r = spacing; r*r < (w*w + h*h); r += spacing) | |
{ | |
tmpCount -= tIncr; | |
drawSomething(-r, 0); | |
} | |
rotate(TAU/radialnum); | |
} | |
} | |
void drawRadialReverse() | |
{ | |
int mult = 0; | |
for(int i = 0; i < radialnum; i++) | |
{ | |
tmpCount = count; | |
mult = (int)floor(sqrt(w*w + h*h) / spacing); | |
for(float r = spacing*mult; r > 0; r -= spacing) | |
{ | |
drawSomething(r, 0); | |
tmpCount += tIncr; | |
} | |
rotate(TAU/radialnum); | |
} | |
tmpCount += tIncr; | |
drawSomething(0, 0); | |
} | |
//does grid, but recursively so tmpcount increments differently | |
void drawRecursive() | |
{ | |
boolean[][] drawn; | |
int dim; | |
if(grid.equals("xy")) | |
{ | |
dim = 1 + 2*(int)floor(h/2/spacing); | |
drawn = new boolean[dim][dim]; | |
drawGridR(false, drawn, dim/2, dim/2); | |
} | |
else if(grid.equals("hex")) | |
{ | |
//because of 30 degree angle, we need 2x as many things | |
dim = 1 + 2*(int)floor(h/spacing); | |
drawn = new boolean[dim][dim]; | |
drawGridR(true, drawn, dim/2, dim/2); | |
} | |
} | |
void drawGridR(boolean hex, boolean[][] drawn, int x, int y) | |
{ | |
if(x < 0 || x >= drawn.length || y < 0 || y >= drawn[x].length || drawn[x][y]) | |
return; | |
tmpCount += tIncr; | |
drawn[x][y] = true; | |
if(hex) | |
{ | |
PVector p = hexPos(x-drawn.length/2, y-drawn[x].length/2, 0); | |
drawSomething(p.x, p.y); | |
} | |
else | |
drawSomething(spacing*(x-drawn.length/2), spacing*(y-drawn[x].length/2)); | |
drawGridR(hex, drawn, x+1, y); | |
drawGridR(hex, drawn, x, y+1); | |
drawGridR(hex, drawn, x-1, y); | |
drawGridR(hex, drawn, x, y-1); | |
} | |
void drawHexGrid() | |
{ | |
PVector p = new PVector(0, 0); | |
drawSomething(p.x, p.y); //centred one | |
tmpCount += tIncr; | |
//# of segments is h/2 / (spacing*sin30), | |
for(int i = 1; i < h / spacing; i++) | |
{ | |
for(int j = 0; j < i; j++) | |
{ | |
p = hexPos(i-j, -i, j); | |
drawSomething(p.x, p.y); | |
p = hexPos(-i+j, i, -j); | |
drawSomething(p.x, p.y); | |
p = hexPos(-i, j, i-j); | |
drawSomething(p.x, p.y); | |
p = hexPos(i, -j, -i+j); | |
drawSomething(p.x, p.y); | |
p = hexPos(j, i-j, -i); | |
drawSomething(p.x, p.y); | |
p = hexPos(-j, -i+j, i); | |
drawSomething(p.x, p.y); | |
} | |
tmpCount += tIncr; | |
} | |
} | |
void drawGrid() | |
{ | |
for(float i = -w/2; i <= w/2; i += spacing) | |
{ | |
for(float j = -h/2; j <= h/2; j += spacing) | |
{ | |
//add option to change from taxi-cab distance to cartesian distance? | |
tmpCount = count + tIncr * (int)round(abs(i/spacing) + abs(j/spacing)); | |
drawSomething(i, j); | |
} | |
} | |
} | |
PVector hexPos(int x, int y, int z) | |
{ | |
PVector vdist = new PVector(spacing*x, 0); | |
PVector vdist2 = new PVector(spacing, 0); | |
vdist2.rotate(2*PI/3); | |
y += x; | |
vdist2.mult(y); | |
vdist.add(vdist2); | |
return vdist; | |
} | |
String filename(int i) | |
{ | |
if(i > 26) | |
return 'z' + filename(i - 26); | |
return ""+(char)('a'+i-1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment