Palm synth
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
// https://imgur.com/a/ZEE2f | |
final color wristCol = color(255, 0, 255); | |
final color palmCol = color(127); | |
final color thumbCol = color(0, 255, 0); | |
final color finger1Col = color(255, 0, 0); | |
final color finger2Col = color(0, 0, 255); | |
final color finger3Col = color(0, 255, 255); | |
final color finger4Col = color(255, 255, 0); | |
color fingerColors[] = { | |
finger1Col, finger2Col, finger3Col, finger4Col | |
}; | |
float fingerDigitRatios[] = { | |
0.86, 1.00, 0.90, 0.65 | |
}; | |
float fingerThicknessRatios[] = { | |
0.94, 1.00, 0.95, 0.87 | |
}; | |
PGraphics medianGraphics; | |
int medianIndexCol[]; | |
boolean bDebugView = false; | |
float targetLenPerUnit = 25.0; | |
float fingerAspect = 4.0; | |
float probs[]; | |
char codes[] = {'W', 'T', 'b', 'B', '1', '2', '3'}; | |
float units[] = {3, 3, 1, 2, 1, 2, 3}; | |
float wristProbability = 0.15; | |
float thumbProbability = 0.20; | |
float blnk1Probability = 0.10; | |
float blnk2Probability = 0.10; | |
float fing1Probability = 0.20; | |
float fing2Probability = 0.20; | |
float fing3Probability = 0.05; | |
int nPalms; | |
String palmStrings[]; | |
Palm palms[]; | |
class Palm { | |
int nSides; | |
String palmCode; | |
char sideTypes[]; | |
float sideUnits[]; | |
int totalUnits; | |
PVector crotchPoints[]; | |
float crotchAngles[]; | |
float angleDeltas[]; | |
float diameter; | |
//----------------------------------------- | |
Palm (String aCode) { | |
diameter = 160; | |
palmCode = aCode; | |
nSides = palmCode.length(); | |
sideTypes = new char[nSides]; | |
sideUnits = new float[nSides]; | |
for (int i=0; i<nSides; i++) { | |
sideTypes[i] = palmCode.charAt(i); | |
for (int k=0; k<codes.length; k++) { | |
if (sideTypes[i] == codes[k]) { | |
sideUnits[i] = units[k]; | |
totalUnits += units[k]; | |
} | |
} | |
} | |
createCrotchPoints(); | |
} | |
//----------------------------------------- | |
void createCrotchPoints() { | |
crotchPoints = new PVector[nSides]; | |
crotchAngles = new float[nSides]; | |
angleDeltas = new float[nSides]; | |
float ang = random(0, TWO_PI); | |
for (int i=0; i<nSides; i++) { | |
crotchAngles[i] = ang; | |
angleDeltas[i] = 0; | |
float px = diameter/2 * cos(ang); | |
float py = diameter/2 * sin(ang); | |
crotchPoints[i] = new PVector(px, py); | |
ang += TWO_PI * (float)sideUnits[i] / (float)totalUnits; | |
} | |
} | |
//----------------------------------------- | |
void updateCrotchPoints() { | |
// compute perimeter | |
float perimeter = 0; | |
for (int i=0; i<nSides; i++) { | |
angleDeltas[i] = 0; | |
PVector pi = crotchPoints[i]; | |
PVector pj = crotchPoints[(i+1)%nSides]; | |
float sideLen = dist(pi.x, pi.y, pj.x, pj.y); | |
perimeter += sideLen; | |
} | |
float lenPerUnit = perimeter/ (float)totalUnits; | |
float error = 0; | |
for (int i=0; i<nSides; i++) { | |
int j = (i+1)%nSides; | |
PVector pi = crotchPoints[i]; | |
PVector pj = crotchPoints[j]; | |
float actualSideLength = dist(pi.x, pi.y, pj.x, pj.y)/lenPerUnit; | |
float targetSideLength = sideUnits[i]; | |
float lenDelta = (targetSideLength - actualSideLength); | |
error += abs(lenDelta); | |
float angi = crotchAngles[i]; | |
float angj = crotchAngles[j]; | |
float dang = angj - angi; | |
float angm = (angi + angj)/2.0; | |
if (j == 0) { | |
angm = (angm+PI)%TWO_PI; | |
} | |
if (dang < 0) { | |
dang += TWO_PI; | |
} | |
angleDeltas[i] -= lenDelta/100.0; | |
angleDeltas[j] += lenDelta/100.0; | |
} | |
for (int i=0; i<nSides; i++) { | |
crotchAngles[i] += angleDeltas[i]; | |
} | |
for (int i=0; i<nSides; i++) { | |
angleDeltas[i] = 0; | |
float px = diameter/2 * cos(crotchAngles[i]); | |
float py = diameter/2 * sin(crotchAngles[i]); | |
crotchPoints[i].x = px; | |
crotchPoints[i].y = py; | |
} | |
// alter the diameter to hold the unit length constant | |
diameter = 0.95*diameter + 0.05*diameter*(targetLenPerUnit/lenPerUnit); | |
} | |
//----------------------------------------- | |
void render() { | |
updateCrotchPoints(); | |
drawThumbs(); | |
drawPalm(); | |
drawFingers(); | |
drawDebug(); | |
} | |
//============================================================ | |
void drawThumbs() { | |
for (int i=0; i<nSides; i++) { | |
if (sideTypes[i] == 'T') { | |
int j = (i+1)%nSides; | |
float pxi = crotchPoints[i].x; | |
float pyi = crotchPoints[i].y; | |
float pxj = crotchPoints[j].x; | |
float pyj = crotchPoints[j].y; | |
int swap = 1; | |
if (random(1.0) < 0.5) { | |
swap = -1; | |
float sx = pxi; | |
float sy = pyi; | |
pxi = pxj; | |
pyi = pyj; | |
pxj = sx; | |
pyj = sy; | |
} | |
float dx = pxj - pxi; | |
float dy = pyj - pyi; | |
float dh = dist(pxi, pyi, pxj, pyj); | |
float thumbThickness = targetLenPerUnit * 0.75; | |
float ux = pxi * (diameter*0.5 - thumbThickness*0.5)/(diameter*0.5); | |
float uy = pyi * (diameter*0.5 - thumbThickness*0.5)/(diameter*0.5); | |
float thumbBaseAng = atan2(dy, dx); | |
float thumbLength = dh; | |
float thang1 = swap * random(-HALF_PI, -PI); | |
float thang2 = swap * random(-PI*0.8, -PI*0.2); | |
float vx = ux + 0.5*thumbLength * cos(thumbBaseAng + 0.5*thang1); | |
float vy = uy + 0.5*thumbLength * sin(thumbBaseAng + 0.5*thang1); | |
float wx = vx + 0.5*thumbLength * cos(thumbBaseAng + 0.5*thang2); | |
float wy = vy + 0.5*thumbLength * sin(thumbBaseAng + 0.5*thang2); | |
noFill(); | |
stroke(thumbCol); | |
strokeCap(ROUND); | |
curveTightness(0.0); | |
// line(ux,uy, vx,vy); | |
// line(vx,vy, wx,wy); | |
strokeWeight(thumbThickness); | |
beginShape(); | |
curveVertex(ux, uy); | |
curveVertex(ux, uy); | |
curveVertex(vx, vy); | |
curveVertex(wx, wy); | |
curveVertex(wx, wy); | |
endShape(); | |
strokeWeight(thumbThickness*0.9); | |
beginShape(); | |
curveVertex(pxi+dx*0.4, pyi+dy*0.4); | |
curveVertex(pxi+dx*0.4, pyi+dy*0.4); | |
curveVertex(vx, vy); | |
curveVertex(wx, wy); | |
curveVertex(wx, wy); | |
endShape(); | |
} | |
} | |
} | |
//============================================================ | |
void drawFingers() { | |
// draw fingers | |
int fingerDir = 1; | |
int fingerCount = 0; | |
int fingerIndex = 0; | |
for (int i=0; i<nSides; i++) { | |
if ((sideTypes[i] >= '1') && (sideTypes[i] <= '3')) { | |
int j = (i+1)%nSides; | |
float pxi = crotchPoints[i].x; | |
float pyi = crotchPoints[i].y; | |
float pxj = crotchPoints[j].x; | |
float pyj = crotchPoints[j].y; | |
float dx = pxj - pxi; | |
float dy = pyj - pyi; | |
float dh = dist(pxi, pyi, pxj, pyj); | |
int nFingers = (int) sideUnits[i]; | |
int fingerCurveDir = (int)((random(1.0) < 0.5) ? -1 : 1); | |
for (int n=0; n<nFingers; n++) { | |
float txa = map(n+0.4, 0, nFingers, pxi, pxj); | |
float tya = map(n+0.4, 0, nFingers, pyi, pyj); | |
float txb = map(n+0.5, 0, nFingers, pxi, pxj); | |
float tyb = map(n+0.5, 0, nFingers, pyi, pyj); | |
float txc = map(n+0.6, 0, nFingers, pxi, pxj); | |
float tyc = map(n+0.6, 0, nFingers, pyi, pyj); | |
int f = n; | |
switch(nFingers) { | |
case 1: | |
fingerIndex = (random(1.0) < 0.5) ? 0:3; | |
break; | |
case 2: | |
case 3: | |
switch (fingerIndex) { | |
case 3: | |
fingerIndex = 2; | |
fingerDir = -1; | |
break; | |
case 0: | |
fingerIndex = 1; | |
fingerDir = 1; | |
break; | |
case 1: | |
case 2: | |
fingerIndex += fingerDir; | |
} | |
break; | |
} | |
f = fingerIndex; | |
float fingerThickness = targetLenPerUnit * 0.75; | |
float fh = fingerThickness * fingerAspect * fingerDigitRatios[f]; | |
float fx = (dy/dh)*fh; | |
float fy = (dx/dh)*fh*(-1); | |
float angi = crotchAngles[i]; | |
float angj = crotchAngles[j]; | |
if (angj < angi) { | |
angj += TWO_PI; | |
} | |
float t01 = map(n+0.5, 0, nFingers, 0, 1); | |
t01 = function_DoubleExponentialOgee (t01, 0.75); | |
float angf = map(t01, 0, 1, angi, angj); | |
fx = fh * cos(angf); | |
fy = fh * sin(angf); | |
noFill(); | |
strokeCap(ROUND); | |
stroke(fingerColors[f]); | |
strokeWeight(fingerThickness * fingerThicknessRatios[f]); | |
curveTightness(0.0); | |
float er = 0.1*fingerCurveDir*random(0, 1); | |
float ex = fx/2 - er*fy; | |
float ey = fy/2 + er*fx; | |
beginShape(); | |
curveVertex(txa, tya); | |
curveVertex(txa, tya); | |
curveVertex(txb+ex, tyb+ey); | |
curveVertex(txb+fx, tyb+fy); | |
curveVertex(txb+fx, tyb+fy); | |
endShape(); | |
beginShape(); | |
curveVertex(txb, tyb); | |
curveVertex(txb, tyb); | |
curveVertex(txb+ex, tyb+ey); | |
curveVertex(txb+fx, tyb+fy); | |
curveVertex(txb+fx, tyb+fy); | |
endShape(); | |
beginShape(); | |
curveVertex(txc, tyc); | |
curveVertex(txc, tyc); | |
curveVertex(txb+ex, tyb+ey); | |
curveVertex(txb+fx, tyb+fy); | |
curveVertex(txb+fx, tyb+fy); | |
endShape(); | |
//line(tx, ty, tx+fx, ty+fy); | |
} | |
} | |
} | |
} | |
//============================================================ | |
void drawPalm() { | |
if (bDebugView) { | |
fill(127, 127); | |
stroke(127); | |
} else { | |
noStroke(); | |
} | |
// draw the Palm polygon | |
curveTightness(0.6666); //mouseX/(float)width); //0.25); //1.0); | |
strokeJoin(ROUND); | |
noStroke(); | |
fill(palmCol); | |
beginShape(); | |
for (int i=-1; i<nSides; i++) { | |
int j=constrain(i, 0, nSides-1); | |
float px = crotchPoints[j].x; | |
float py = crotchPoints[j].y; | |
curveVertex(px, py); | |
} | |
curveVertex(crotchPoints[0].x, crotchPoints[0].y); | |
curveVertex(crotchPoints[1].x, crotchPoints[1].y); | |
endShape(CLOSE); | |
strokeWeight(targetLenPerUnit * 0.15); | |
stroke(127); | |
noFill(); | |
beginShape(); | |
for (int i=-1; i<nSides; i++) { | |
int j=constrain(i, 0, nSides-1); | |
float px = crotchPoints[j].x; | |
float py = crotchPoints[j].y; | |
curveVertex(px, py); | |
} | |
curveVertex(crotchPoints[0].x, crotchPoints[0].y); | |
curveVertex(crotchPoints[1].x, crotchPoints[1].y); | |
endShape(CLOSE); | |
} | |
//============================================================ | |
void drawDebug() { | |
if (bDebugView) { | |
noFill(); | |
stroke(127); | |
strokeWeight(1); | |
ellipse(0, 0, diameter, diameter); | |
for (int i=0; i<nSides; i++) { | |
int j = (i+1)%nSides; | |
float pxi = crotchPoints[i].x; | |
float pyi = crotchPoints[i].y; | |
float pxj = crotchPoints[j].x; | |
float pyj = crotchPoints[j].y; | |
fill(127); | |
for (int n=0; n<=sideUnits[i]; n++) { | |
float tx = map(n, 0, sideUnits[i], pxi, pxj); | |
float ty = map(n, 0, sideUnits[i], pyi, pyj); | |
ellipse(tx, ty, 3, 3); | |
} | |
} | |
textAlign(CENTER); | |
for (int i=0; i<nSides; i++) { | |
float px = crotchPoints[i].x; | |
float py = crotchPoints[i].y; | |
stroke(160); | |
line (0, 0, px, py); | |
fill(255); | |
text(sideTypes[i], px, py); | |
// text(i, px, py);// ":" + sideUnits[i] | |
} | |
fill(255); | |
text(palmCode, 0, diameter/2 + 20); | |
} | |
} | |
} | |
//============================================================ | |
void setup() { | |
size(600, 600); | |
medianGraphics = createGraphics(width, height); | |
noSmooth(); | |
medianIndexCol = new int[49]; | |
makeProbs(); | |
nPalms = 4; | |
palmStrings = new String[nPalms]; | |
palms = new Palm[nPalms]; | |
for (int i=0; i<nPalms; i++) { | |
// Generate palmCodes, like 3T1W, TW230, 1TW0TW | |
int nPalmSides = (int)random(5.0, 7.5); | |
String palmStr = ""; | |
char prevElementChar = ' '; | |
for (int j=0; j<nPalmSides; j++) { | |
char elementChar = ' '; | |
do { | |
float r = random(1.0); | |
int k = 0; | |
while (r > probs[k]) { | |
k++; | |
} | |
k--; | |
elementChar = codes[k]; | |
} while ((elementChar == prevElementChar) || (abs(elementChar-prevElementChar) == 32)); | |
palmStr += elementChar; | |
prevElementChar = elementChar; | |
} | |
palms[i] = new Palm(palmStr); | |
} | |
} | |
void draw() { | |
background(0); | |
randomSeed(hour() + minute() + second()); | |
for (int i=0; i<nPalms; i++) { | |
float cx = (i%2+1)*(width/3);//+(width/4); | |
float cy = (i/2+1)*(width/3);//+(width/4); | |
pushMatrix(); | |
translate(cx, cy); | |
palms[i].render(); | |
popMatrix(); | |
} | |
boolean bComputeMedianImage = false; | |
if (bComputeMedianImage) { | |
loadPixels(); | |
//medianGraphics | |
medianGraphics.beginDraw(); | |
medianGraphics.background(0); | |
medianGraphics.loadPixels(); | |
int index; | |
int indexij; | |
for (int y=3; y<(height-3); y++) { | |
for (int x=3; x<(width-3); x++) { | |
int medi = 0; | |
for (int i=(y-3); i<=(y+3); i++) { | |
for (int j=(x-3); j<=(x+3); j++) { | |
indexij = i*width + j; | |
int pixelAtij = pixels[indexij]; | |
switch(pixelAtij) { | |
case -16777216: // black | |
medianIndexCol[medi] = -1; | |
break; | |
case -65281: //wristCol: | |
medianIndexCol[medi] = 0; | |
break; | |
case -8421505: //palmCol: | |
medianIndexCol[medi] = 1; | |
break; | |
case -16711936: //thumbCol: | |
medianIndexCol[medi] = 2; | |
break; | |
case -65536: //finger1Col: | |
medianIndexCol[medi] = 3; | |
break; | |
case -16776961: //finger2Col: | |
medianIndexCol[medi] = 4; | |
break; | |
case -16711681: //finger3Col: | |
medianIndexCol[medi] = 5; | |
break; | |
case -256: //finger4Col: | |
medianIndexCol[medi] = 6; | |
break; | |
} | |
medi++; | |
} | |
} | |
index = y * width + x; | |
medianIndexCol = sort(medianIndexCol); | |
switch(medianIndexCol[24]) { | |
case -1: | |
medianGraphics.pixels[index] = color(0); | |
break; | |
case 0: | |
medianGraphics.pixels[index] = wristCol; | |
break; | |
case 1: | |
medianGraphics.pixels[index] = palmCol; | |
break; | |
case 2: | |
medianGraphics.pixels[index] = thumbCol; | |
break; | |
case 3: | |
medianGraphics.pixels[index] = finger1Col; | |
break; | |
case 4: | |
medianGraphics.pixels[index] = finger2Col; | |
break; | |
case 5: | |
medianGraphics.pixels[index] = finger3Col; | |
break; | |
case 6: | |
medianGraphics.pixels[index] = finger4Col; | |
break; | |
} | |
} | |
} | |
medianGraphics.updatePixels(); | |
medianGraphics.endDraw(); | |
image(medianGraphics, width/2, 0); | |
} | |
} | |
void makeProbs() { | |
probs = new float[8]; | |
int c = 0; | |
probs[c] = 0; | |
c++; | |
probs[c] = probs[c-1] + wristProbability; | |
c++; | |
probs[c] = probs[c-1] + thumbProbability; | |
c++; | |
probs[c] = probs[c-1] + blnk1Probability; | |
c++; | |
probs[c] = probs[c-1] + blnk2Probability; | |
c++; | |
probs[c] = probs[c-1] + fing1Probability; | |
c++; | |
probs[c] = probs[c-1] + fing2Probability; | |
c++; | |
probs[c] = probs[c-1] + fing3Probability; | |
c++; | |
println("Checksum = " + probs[7]); | |
} | |
//------------------------------------------------------------------ | |
float function_DoubleExponentialOgee (float x, float a) { | |
float min_param_a = 0.0 + EPSILON; | |
float max_param_a = 1.0 - EPSILON; | |
a = constrain(a, min_param_a, max_param_a); | |
float y = 0; | |
if (x<=0.5) { | |
y = (pow(2.0*x, 1.0-a))/2.0; | |
} else { | |
y = 1.0 - (pow(2.0*(1.0-x), 1.0-a))/2.0; | |
} | |
return y; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment