/** * Biopsy Machine * by Andres Colubri. December 2007. * * Generates a clockwork mechanism made out of semi-rings and * needle-like structures. The mechanism appears and grows when the mouse is * pressed or the character 49 is received by the serial port. * */ /* License (based on zlib/libpng): Copyright (c) 2007 Andres Colubri. This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely. */ // Commented out for online version, but used in installation. //import processing.opengl.*; // Commented out for online version, but used in installation. //import processing.serial.*; float prjCenterX = 25; float prjCenterY = 22; float prjDiameter = 750; float lineWidth = 2.0; float transparency0 = 0.7; float decayConst = 0.009; float sinLUT[]; float cosLUT[]; float SINCOS_PRECISION = 0.1f; int SINCOS_LENGTH = (int) (360f / SINCOS_PRECISION); // Commented out for online version, but used in installation. //Serial port; class biopsyNeedle { biopsyNeedle() { needleEnabled = false; animatingNeedle = false; animatingTransparency = false; } biopsyNeedle(float x, float y, float r, float rspeed, float nspeed, float ndist) { restart(x, y, r, rspeed, nspeed, ndist); } void restart(float x, float y, float r, float rspeed, float nspeed, float ndist) { xCenter = x; yCenter = y; machineRadius = r; needleEnabled = false; animatingNeedle = false; animatingTransparency = false; rotationAngle_NonEased = 0.0; needleMotion_NonEased = 0; rotationAngle = 0; needleMotion = 0; transparency = transparency0; rotationAngleSpeed = rspeed; needleMotionSpeed = nspeed; transparencySpeed = 0.001; transparencyDecayConst = decayConst; rotationAngleEaseFactor = 0.001; needleMotionEaseFactor = 0.001; needleMinDist = ndist; initCircle(); initNeedle(); startTime = millis(); } void initCircle() { circleIsGrown = false; circleWidth = random(5, 10); circleRadius0 = machineRadius - circleWidth; circleRadius1 = machineRadius + circleWidth; rotationAngle = random(PI, TWO_PI); float a = random(PI, TWO_PI); circleAngleMin = random(0, TWO_PI - a); circleAngleMax = circleAngleMin + a; circleAngle0 = circleAngle0_NonEased = 0.5 * (circleAngleMin + circleAngleMax); circleAngle1 = circleAngle1_NonEased = 0.5 * (circleAngleMin + circleAngleMax); circleAngleStep = circleEaseFactor = 0.01; circleBorderFactor = 0.1; circleBorderAngle0 = (1.0 + circleBorderFactor) * circleAngleMin; circleBorderAngle1 = circleAngleMax - (circleBorderAngle0 - circleAngleMin); sinBorderAngle0 = sin(circleBorderAngle0); cosBorderAngle0 = cos(circleBorderAngle0); sinBorderAngle1 = sin(circleBorderAngle1); cosBorderAngle1 = cos(circleBorderAngle1); } void initNeedle() { needleIsGrown = false; needleBorderFactor = 0.3; needleAngle = random((1.0 + needleBorderFactor) * circleAngleMin, (1.0 - needleBorderFactor) * circleAngleMax); needleSin = sin(needleAngle); needleCos = cos(needleAngle); needleXtang = needleCos; needleYtang = needleSin; needleXnorm = -needleYtang; needleYnorm = needleXtang; needleTipLengthMax = random(20, 30); needleWidth = random(0.4 * circleWidth, 0.6 * circleWidth); needleTipCurvature = 0.9 * needleWidth; needleTipAngle0 = PI; needleTipAngle1 = TWO_PI; needleTipAngleMax = 0.3 * PI; needleTipLength = 0.0; needleTipLengthStep = 0.1; needleTipAngleStep = 0.01; needleRadius0 = needleRadius0_NonEased = 0; needleRadius1 = needleRadius1_NonEased = 0; needleRadiusMin = 5; needleRadiusMax = 350; needleRadiusStep = 1.0; needleEaseFactor = 0.01; axisX = needleCos * machineRadius; axisY = needleSin * machineRadius; axisRadius = 0.0; axisRadiusFactor = 0.8; axisRadiusStep = 0.1; axisLinearToAngFact = 1.0 / 20.0; } void update() { if (!circleIsGrown) growCircle(); if (!needleIsGrown) growNeedle(); if (!animatingTransparency) animatingTransparency = true; animateRotation(); if (animatingNeedle) animateNeedle(); if (animatingTransparency) animateTransparency(); } void draw() { drawNeedle(); drawCircle(); drawAxis(); } void drawNeedle() { if (!needleEnabled) return; pushMatrix(); translate(xCenter, yCenter); rotate(rotationAngle); float x0, y0, x1, y1; float xtip, ytip; float cxtip0, cytip0, cxtip1, cytip1; needleRadius0 += needleMotion; needleRadius1 -= needleMotion; x0 = needleCos * (machineRadius - needleRadius0); y0 = needleSin * (machineRadius - needleRadius0); x1 = needleCos * (machineRadius + needleRadius1); y1 = needleSin * (machineRadius + needleRadius1); needleRadius0 -= needleMotion; needleRadius1 += needleMotion; xtip = x0 - needleTipLength * needleXtang; ytip = y0 - needleTipLength * needleYtang; cxtip0 = xtip + needleTipCurvature * (needleXnorm * needleTipCos0 + needleYnorm * needleTipSin0); cytip0 = ytip + needleTipCurvature * (needleYnorm * needleTipCos0 - needleXnorm * needleTipSin0); cxtip1 = xtip + needleTipCurvature * (needleXnorm * needleTipCos1 + needleYnorm * needleTipSin1); cytip1 = ytip + needleTipCurvature * (needleYnorm * needleTipCos1 - needleXnorm * needleTipSin1); stroke(0, transparency); strokeWeight(lineWidth); // Sides. line(x0 - needleWidth * needleXnorm, y0 - needleWidth * needleYnorm, x1 - needleWidth * needleXnorm, y1 - needleWidth * needleYnorm); line(x0 + needleWidth * needleXnorm, y0 + needleWidth * needleYnorm, x1 + needleWidth * needleXnorm, y1 + needleWidth * needleYnorm); // Tip. bezier(x0 - needleWidth * needleXnorm, y0 - needleWidth * needleYnorm, x0 - needleWidth * needleXnorm - needleTipCurvature * needleXtang, y0 - needleWidth * needleYnorm - needleTipCurvature * needleYtang, cxtip0, cytip0, xtip, ytip); bezier(x0 + needleWidth * needleXnorm, y0 + needleWidth * needleYnorm, x0 + needleWidth * needleXnorm - needleTipCurvature * needleXtang, y0 + needleWidth * needleYnorm - needleTipCurvature * needleYtang, cxtip1, cytip1, xtip, ytip); // Bottom. line(x1 - needleWidth * needleXnorm, y1 - needleWidth * needleYnorm, x1 + needleWidth * needleXnorm, y1 + needleWidth * needleYnorm); popMatrix(); } void drawCircle() { stroke(1.0); fill(1.0); pushMatrix(); translate(xCenter, yCenter); rotate(rotationAngle); int startLUT = (int) (0.5f + (circleAngle0 / TWO_PI) * SINCOS_LENGTH); int stopLUT = (int) (0.5f + (circleAngle1 / TWO_PI) * SINCOS_LENGTH); beginShape(QUAD_STRIP); int increment = 1; for (int i = startLUT; i < stopLUT; i += increment) { int ii = i % SINCOS_LENGTH; vertex(cosLUT[ii] * circleRadius0, sinLUT[ii] * circleRadius0); vertex(cosLUT[ii] * circleRadius1, sinLUT[ii] * circleRadius1); } vertex(cosLUT[stopLUT % SINCOS_LENGTH] * circleRadius0, sinLUT[stopLUT % SINCOS_LENGTH] * circleRadius0); vertex(cosLUT[stopLUT % SINCOS_LENGTH] * circleRadius1, sinLUT[stopLUT % SINCOS_LENGTH] * circleRadius1); endShape(); stroke(0, transparency); strokeWeight(lineWidth); noFill(); line(circleCos0 * circleRadius0, circleSin0 * circleRadius0, circleCos0 * circleRadius1, circleSin0 * circleRadius1); line(circleCos1 * circleRadius0, circleSin1 * circleRadius0, circleCos1 * circleRadius1, circleSin1 * circleRadius1); arc(0, 0, 2 * circleRadius0, 2 * circleRadius0, circleAngle0, circleAngle1); arc(0, 0, 2 * circleRadius1, 2 * circleRadius1, circleAngle0, circleAngle1); if (circleAngle0 <= circleBorderAngle0) { line(cosBorderAngle0 * circleRadius0, sinBorderAngle0 * circleRadius0, cosBorderAngle0 * circleRadius1, sinBorderAngle0 * circleRadius1); } if (circleBorderAngle1 <= circleAngle1) { line(cosBorderAngle1 * circleRadius0, sinBorderAngle1 * circleRadius0, cosBorderAngle1 * circleRadius1, sinBorderAngle1 * circleRadius1); } popMatrix(); } void drawAxis() { if (!needleEnabled) return; pushMatrix(); translate(xCenter, yCenter); rotate(rotationAngle); stroke(0, transparency); strokeWeight(lineWidth); fill(1.0); ellipse(axisX, axisY, 2 * axisRadius, 2 * axisRadius); float a = needleAngle + TWO_PI * (axisLinearToAngFact * needleMotion); float y = sin(a) * axisRadius; float x = cos(a) * axisRadius; line(axisX - x , axisY - y, axisX + x , axisY + y); popMatrix(); } void growCircle() { if (circleAngleMin < circleAngle0_NonEased) circleAngle0_NonEased -= circleAngleStep; if (abs(circleAngle0_NonEased - circleAngle0) > circleAngleStep) { circleAngle0 += (circleAngle0_NonEased - circleAngle0) * circleEaseFactor; circleSin0 = sin(circleAngle0); circleCos0 = cos(circleAngle0); } if (circleAngle1_NonEased < circleAngleMax) circleAngle1_NonEased += circleAngleStep; if (abs(circleAngle1_NonEased - circleAngle1) > circleAngleStep) { circleAngle1 += (circleAngle1_NonEased - circleAngle1) * circleEaseFactor; circleSin1 = sin(circleAngle1); circleCos1 = cos(circleAngle1); } boolean reachedAngle0 = abs(circleAngleMin - circleAngle0) < circleAngleStep; boolean reachedAngle1 = abs(circleAngleMax - circleAngle1) < circleAngleStep; if (reachedAngle0 && reachedAngle1) circleIsGrown = true; if (((1.0 + needleBorderFactor) * circleAngle0 < needleAngle) && (needleAngle < (1.0 - needleBorderFactor) * circleAngle1)) { needleEnabled = true; animatingNeedle = true; } } void growNeedle() { if (!needleEnabled) return; boolean reachedAxisRadius = axisRadius >= axisRadiusFactor * circleWidth; if (!reachedAxisRadius) axisRadius += axisRadiusStep; boolean reachedAngle0 = PI - needleTipAngleMax >= needleTipAngle0; if (!reachedAngle0) { needleTipAngle0 -= needleTipAngleStep; needleTipSin0 = sin(needleTipAngle0); needleTipCos0 = cos(needleTipAngle0); } boolean reachedAngle1 = needleTipAngle1 >= TWO_PI + needleTipAngleMax; if (!reachedAngle1) { needleTipAngle1 += needleTipAngleStep; needleTipSin1 = sin(needleTipAngle1); needleTipCos1 = cos(needleTipAngle1); } boolean reachedLength = needleTipLength >= needleTipLengthMax; if (!reachedLength) needleTipLength += needleTipLengthStep; if (needleRadius0_NonEased < needleRadiusMin) needleRadius0_NonEased += needleRadiusStep; if (abs(needleRadius0_NonEased - needleRadius0) > needleRadiusStep) { needleRadius0 += (needleRadius0_NonEased - needleRadius0) * needleEaseFactor; } if (needleRadius1_NonEased < needleRadiusMax) needleRadius1_NonEased += needleRadiusStep; if (abs(needleRadius1_NonEased - needleRadius1) > needleRadiusStep) { needleRadius1 += (needleRadius1_NonEased - needleRadius1) * needleEaseFactor; } boolean reachedRadius0 = abs(needleRadiusMin - needleRadius0) <= needleRadiusStep; boolean reachedRadius1 = abs(needleRadiusMax - needleRadius1) <= needleRadiusStep; if (reachedAxisRadius && reachedAngle0 && reachedAngle1 && reachedLength && reachedRadius0 && reachedRadius1) needleIsGrown = true; } void animateRotation() { rotationAngle_NonEased += rotationAngleSpeed; if (abs(rotationAngle_NonEased - rotationAngle) > rotationAngleSpeed) { rotationAngle += (rotationAngle_NonEased - rotationAngle) * rotationAngleEaseFactor; } } void animateNeedle() { needleMotion_NonEased += needleMotionSpeed; if (abs(needleMotion_NonEased - needleMotion) > needleMotionSpeed) { needleMotion += (needleMotion_NonEased - needleMotion) * needleMotionEaseFactor; } if ((machineRadius - needleRadius0 - needleMotion - needleTipLength) < needleMinDist) { needleMotion = machineRadius - needleTipLength - needleMinDist - needleRadius0; animatingNeedle = false; } } void animateTransparency() { float diff = (millis() - startTime) / 1000.0; transparency = transparency0 * exp(-transparencyDecayConst * diff); if (transparency < 0.0001) { transparency = 0; animatingTransparency = false; } } boolean dissapeared() { return transparency == 0; } float xCenter, yCenter; float machineRadius; int startTime; float rotationAngle; float needleMotion; float transparency; float rotationAngle_NonEased; float needleMotion_NonEased; float rotationAngleSpeed; float needleMotionSpeed; float transparencySpeed; float transparencyDecayConst; float rotationAngleEaseFactor; float needleMotionEaseFactor; boolean animatingNeedle; boolean animatingTransparency; boolean circleIsGrown; float circleAngleMin, circleAngleMax; float circleRadius0, circleRadius1; float circleAngle0, circleAngle1; float circleAngle0_NonEased, circleAngle1_NonEased; float circleAngleStep, circleEaseFactor; float circleSin0, circleCos0; float circleSin1, circleCos1; float circleWidth; float circleBorderFactor; float circleBorderAngle0; float circleBorderAngle1; float sinBorderAngle0, cosBorderAngle0; float sinBorderAngle1, cosBorderAngle1; boolean needleEnabled; boolean needleIsGrown; float needleAngle; float needleSin, needleCos; float needleXtang, needleYtang; float needleXnorm, needleYnorm; float needleTipLength, needleTipLengthMax; float needleTipAngle0, needleTipAngle1; float needleRadius0, needleRadius1; float needleRadius0_NonEased, needleRadius1_NonEased; float needleWidth, needleTipCurvature; float needleTipAngleMax; float needleTipLengthStep, needleTipAngleStep; float needleTipSin0, needleTipCos0; float needleTipSin1, needleTipCos1; float needleRadiusStep, needleEaseFactor; float needleRadiusMin, needleRadiusMax; float needleBorderFactor; float needleMinDist; float axisX, axisY, axisRadius; float axisRadiusFactor, axisRadiusStep; float axisLinearToAngFact; } class biopsyMachine { biopsyMachine(int n, float x, float y, float r0, float dr) { needles = new biopsyNeedle[n]; float r = r0; for (int i = 0; i < n; i++) { int dir; if (random(1.0) < 0.5) dir = -1; else dir = +1; needles[i] = new biopsyNeedle(x, y, r, dir * random(0.001, 0.002), random(0.1, 0.2), (i + 1) * random(30, 50)); r += dr; } } void restart(float x, float y, float r0, float dr) { float r = r0; int dir; if (random(1.0) < 0.5) dir = -1; else dir = +1; for (int i = 0; i < needles.length; i++) { needles[i].restart(x, y, r, dir * random(0.001, 0.002), random(0.1, 0.2), (i + 1) * random(30, 50)); r += dr; dir *= -1; } } void update() { for (int i = 0; i < needles.length; i++) needles[i].update(); } void draw() { for (int i = 0; i < needles.length; i++) needles[i].drawNeedle(); for (int i = 0; i < needles.length; i++) { needles[i].drawCircle(); needles[i].drawAxis(); } } biopsyNeedle[] needles; } biopsyMachine bio; void setup() { // The OPENGL renderer was used in the installation (with the smooth // option enabled). P3D is used here to avoid problems in the online // version. //size(1024, 768, OPENGL); //size(screen.width, screen.height, OPENGL); size(400, 400, P3D); colorMode(RGB, 1.0); background(1.0); noFill(); //smooth(); noCursor(); bio = new biopsyMachine(2, 0.5 * width, 0.5 * height, 150, 80); // Initializing sine and cosine lookup tables. sinLUT = new float[SINCOS_LENGTH]; cosLUT = new float[SINCOS_LENGTH]; for (int i = 0; i < SINCOS_LENGTH; i++) { sinLUT[i] = (float) Math.sin(i * DEG_TO_RAD * SINCOS_PRECISION); cosLUT[i] = (float) Math.cos(i * DEG_TO_RAD * SINCOS_PRECISION); } // Uses the first port in this list (number 0). Change this to // select the port corresponding to your Arduino board. The last // parameter (e.g. 9600) is the speed of the communication. It // has to correspond to the value passed to Serial.begin() in your // Arduino sketch. // Commented out for online version, but used in installation. //port = new Serial(this, Serial.list()[1], 9600); } void mousePressed() { bio.restart(0.5 * width, 0.5 * height, 150, 80); } void draw() { background(0.0); // Commented out for online version, but used in installation. /* if (0 < port.available()) { int val = port.read(); if (val == 49) { delay(2000); bio.restart(0.5 * width, 0.5 * height, 150, 80); } } */ translate(prjCenterX, prjCenterY); stroke(0); fill(1.0); ellipse(0.5 * width, 0.5 * height, prjDiameter, prjDiameter); bio.update(); bio.draw(); }