/* Definitely opportunity to make faster using pointers in c++. IDEAS - control parameters with XY boxes (what are these called?) - model each muscle: randomly connect each to one of a few nerves - controller is guts of WiiMote stuffed into a wooden shell, each controller has a knob which adjust one of the slider parameters (running average & connection wieght) - one controllers "paints" bass and the other treble, in the form of stimulation. DONE - load function - make connections bi-directional - make parameter changing interface (needs to work online) - chroma muscle jitter - fix loading connectivity issue, where all chromas are connected to a single phantom chroma (at last location clicked?) - muscle shifting - diagnose over-connection bug - changed loadNetwork to use loadStrings instead of buffered reader for JS compat - threaded version of connection drawer significantly speeds up drawing all connections - oh but using P2D is waaay faster, without threading! - microphone input TODO - make bidirectionality happen in getAct(), 1/2 as many computations - add reference frame for twitch, so start/stop not so abrupt - update loop separate from draw; have chroma.update() - OSC for parameter control - random chroma act "pop" when no interaction for awhile, or maybe parameters drift around? - background color slider - convert colors to HSB - interface for changing inter-layer weights: correlation matrix, right/left click each box to increment / decrement weight for that combo. Triangle for bi-dir, square for uni-dir - select which layer is stimulated by controller - save parameters on exit, load on startup - drive each chroma class with a different fourier band - fade slider which goes to 0 - thread for sound analysis? */ boolean debugSpawning = false; boolean debugConnections = false; // Visuals int numChromaSides = 5; // sides per chroma float diaJitter = 0.3; // percent randomness in diameters float muscleJitter = 0.05; // percent randomness in each muscle each timestep boolean toDrawConnections = false; int framesBtwFades = 1; // number of frames btw screen fades int fadeTrans = 80; // alpha value of each fade color bgColor; // Layers int numLayers = 3; // number of different chromatophore layers int[] minDias = {16, 10, 6}; // vector of minimum diameters TODO this is radius if using vertices int[] maxDias = {60, 40, 20}; int[] interDist = {100, 50, 22};// minimum distance btw chromas in the various layers int trans = 200; // transparency factor float[][] layerWs = { {0.5, 0.5, 0.5}, {0.5, 0.5, 0.5}, {0.5, 0.5, 0.5} }; color[] colors; // light version color bgColorLight = color(224, 236, 207); // background color color[] colorsLight = { color(65, 40, 50, trans), color(130, 60, 50, trans), color(210, 190, 80, trans) }; // dark version color bgColorDark = color(0); // background color color[] colorsDark = { color(230, 30, 255, trans), color(255, 140, 80, trans), color(255, 255, 210, trans) }; boolean[] toDrawConnsLayers = new boolean[numLayers]; // which layer connections to draw int curLayer = 0; // number of current layer being populated // Dynamics boolean useElectrodStim = true; // whether to stimulate locally, simulating a ball electrode placed on skin int localStimTightness = 20; // affect radius of stimulation (larger number = tighter radius) float localStimAmp = 0.75; float localStimPeriod = 10; // msec float newChromaAct = 0.01; // activation level of brand new chromatophores boolean recordHistory = false; // to record recent activation history of each chroma int numHistoryTPts = 100; float dec = 1.0; // leakage current float weight = 0.071; // how chromas affect each other boolean useDistWeight = false; // whether to calc weights btw chromas using the distance btw them float runningActW = 0.02; // weight on new data in calculating average activity float warble; // amount of radial jitter, in percent int lastStimTime = 0; boolean stimReady = false; float stimAmp_1 = 0.0; // amplitude of stimulator 1 float stimAmp_3 = 0.0; // amplitude of stimulator 1 float fftRunningAvg = 0.0; // muscle shifting boolean toShift = false; float shiftPeriod = 90.0; // skin muscle shift time constant float shiftAmpMax = 6.0; float shiftAmpMin = 0.1; float shiftAmp = 0.1; // float shiftPosFactor = 0.0; float shiftAmpAcc = 0.1; float shiftStimMod = 0.0; // stim factor float shiftStimModMax = 24.0; float shiftStimModMin = 10.0; float shiftStimRelaxFactor = 0.99; // relaxtion rate when stim released float cellSize = 700.0; // size of shift pattern float spatialComponent = PI/cellSize; // used inside shift generator int maxShift = int(shiftAmp + shiftStimModMax); // for placing chromas outside window bounds // I/O1 String networkFileName = "chromas.txt"; PrintWriter output; // Population int maxChromas = 7000; // number of chromas allowed int maxChromasJS= 1000; // number of chromas allowed javascript int curChroma = 0; // currently active chroma int numChromas = 0; // number of chromas placed Chroma[] chromas = new Chroma[maxChromas]; // Interface int barH = 48; int barY = barH; int barAlpha = 200; float curBarAlpha = 200.0; PFont font; ArrayList sliders; ArrayList checkboxes; boolean interfaceOn = false; boolean indicateWeight = true; long indicateWeightTime = 0; // OSC import oscP5.*; import netP5.*; OscP5 osc; float wiiX_1, wiiY_1 = 0; float wiiX_3, wiiY_3 = 0; boolean stim1 = false; boolean stim3 = false; // Audio import ddf.minim.analysis.*; import ddf.minim.*; Minim minim; AudioInput lineIn; //FFT fft; FFT fftLog; // Other int maxAttempts = 32; // maximum number of attempts to find a spawn pt boolean isRunning = true; int lastChromaAddedT = 0; boolean isAdding = true; float diag; int lastStatusTime = 0; boolean isJava = false; PGraphics imgConn; boolean working = false; boolean useThreading = false; /*void init() { //frame.setUndecorated(true); super.init(); }*/ void setup() { // Check if java or javascript try { if (javaVersionName != null) { isJava = true; } } catch(Exception e) { //Processing.logger = console; // won't compile in java, very annoying //println("running in javascript mode"); } if(isJava) { //size(600, 440, P2D); // P2D is way faster, slightly less smooth //size(1600, 1200, P2D); // tristan's projector //size(1024, 768, P2D); size(1280, 720, P2D); // acer projector frameRate(300); bgColor = bgColorDark; colors = colorsDark; } else { size(900, 600); frameRate(60); maxChromas = maxChromasJS; networkFileName = "chromas_js.txt"; bgColor = bgColorLight; colors = colorsLight; } // Background background(bgColor); smooth(); diag = sqrt(sq(width)+sq(height)); imgConn = createGraphics(width, height); if(useThreading) thread("drawConnectionsThread"); // Fonts font = createFont("Gill Sans MT", 10); textFont(font, 10); // Interface sliders = new ArrayList(); sliders.add(new Slider("refractory speed", 10, 10, 140, 20, runningActW, 0.0, 1.0, 3.0)); sliders.add(new Slider("connection weight", 160, 10, 140, 20, weight, 0.0, 1.0, 3.0)); color sliderBarColor = color(50, curBarAlpha); for(Slider curSlider : sliders) { curSlider.draw(sliderBarColor); } checkboxes = new ArrayList(); checkboxes.add(new Checkbox("black", 310, 10, 30, 20, false)); checkboxes.add(new Checkbox("red", 350, 10, 30, 20, false)); checkboxes.add(new Checkbox("yellow", 390, 10, 30, 20, false)); checkboxes.add(new Checkbox("twitch", 440, 10, 30, 20, toShift)); checkboxes.add(new Checkbox("reset", 480, 10, 30, 20, false)); for(Checkbox curCheckbox : checkboxes) { curCheckbox.draw(); } // OSC if(isJava) { osc = new OscP5(this,9000); osc.plug(this,"infrared_1" , "/wii/1/ir"); osc.plug(this,"stimButton_1", "/wii/1/button/A"); osc.plug(this,"refractUp", "/wii/1/button/Plus"); osc.plug(this,"refractDown", "/wii/1/button/Minus"); osc.plug(this,"infrared_3" , "/wii/3/ir"); osc.plug(this,"stimButton_3", "/wii/3/button/A"); osc.plug(this,"connectionUp", "/wii/3/button/Plus"); osc.plug(this,"connectionDown", "/wii/3/button/Minus"); // Audio minim = new Minim(this); lineIn = minim.getLineIn(); // use the getLineIn method of the Minim object to get an AudioInput fftLog = new FFT( lineIn.bufferSize(), lineIn.sampleRate() ); // create an FFT object for calculating logarithmically spaced averages fftLog.logAverages( 22, 3 ); } // initialize chroma network if(isJava) loadNetwork(networkFileName); else growSeedChroma(); // add first chroma } void draw() { // Fade all graphics if(frameCount % framesBtwFades == 0 && !(useThreading && toDrawConnections)) { fill(bgColor, fadeTrans); rect(0, 0, width, height); } // Stimulation // audio if(isJava) { fftLog.forward( lineIn.left ); float newAvg = fftLog.getAvg(12)/10.0; fftRunningAvg = 0.0002*newAvg + 0.9998*fftRunningAvg; //localStimAmp = constrain(newAvg - fftRunningAvg, 0, 10); stimAmp_1 = fftLog.getAvg(2); stimAmp_3 = fftLog.getAvg(18); //println(nf(localStimAmp,0,8)+", "+nf(stimAmp_1,0,2) ); } // position electrode ellipseMode(CENTER); stroke(130, 255, 150); strokeWeight(3); noFill(); ellipse(wiiX_1, wiiY_1, 20, 20); noStroke(); strokeWeight(1); ellipseMode(CENTER); stroke(160, 150, 255); strokeWeight(3); noFill(); ellipse(wiiX_3, wiiY_3, 20, 20); noStroke(); strokeWeight(1); // stim hz if(!stimReady && mousePressed) { if(millis()-lastStimTime > localStimPeriod) { stimReady = true; lastStimTime = millis(); } } else { stimReady = false; } // Skin muscle shifts if(mousePressed || stim1 || stim3) { // activate single chroma int overChromaNum = overAny(); if(overChromaNum != -1) { chromas[overChromaNum].act += 0.3; } // low freq muscle //shiftStimMod = (shiftStimMod+0.5)*1.25; shiftStimMod = shiftStimMod + 0.008*shiftStimMod*(shiftStimModMax-shiftStimMod); // easing: sigmoid diff eq } else shiftStimMod = shiftStimMod + 0.008*shiftStimMod*(shiftStimModMin-shiftStimMod); //(shiftStimMod-shiftStimModMin)*shiftStimRelaxFactor; shiftStimMod = constrain(shiftStimMod, shiftStimModMin, shiftStimModMax); //println(nf(shiftStimMod,0,1)); shiftPosFactor = 0.96*(shiftStimMod-shiftStimModMin)/(shiftStimModMax-shiftStimModMin); if(toShift) { shiftAmp *= 1+shiftAmpAcc; } else { shiftAmp *= 1-shiftAmpAcc; } shiftAmp = constrain(shiftAmp, shiftAmpMin, shiftAmpMax); //sin(millis()/70.0))/40.0; // Add new chroma every n msec if(isAdding) { int curTimeSinceAdd = millis() - lastChromaAddedT; if(numChromas 10 && curTimeSinceAdd < 3000) { addChroma(); } if(curTimeSinceAdd > 2000 && curLayer < numLayers-1) { curLayer++; //println("now adding layer "+curLayer); lastChromaAddedT = millis(); } if(curTimeSinceAdd > 8000) { //println("stop trying to add chromas"); isAdding = false; } } } // Draw! if(toDrawConnections && useThreading) { image(imgConn, 0, 0); if(working) { } else { thread("drawConnectionsThread"); } } drawChromas(); // Interface noStroke(); // fade bar in if(mouseY < barY) { interfaceOn = true; curBarAlpha = constrain( (curBarAlpha+4) * 1.1, 0, barAlpha); fill(20, curBarAlpha); rectMode(CORNER); rect(0, 0, width, barY); // elements for(Slider curSlider : sliders) { color sliderBarColor = color(220, curBarAlpha); if(curSlider.over()) { //println(curSlider.name+": "+nf(curSlider.getPercent(),0,2)); if(curSlider.name.equals("refractory speed")) { if(mousePressed) runningActW = curSlider.getValue(); sliderBarColor = color(120, 100, 80, curBarAlpha); } else if(curSlider.name.equals("connection weight")) { if(mousePressed) weight = curSlider.getValue(); sliderBarColor = color(120, 100, 80, curBarAlpha); } } else { //sliderBarColor = color(100, curBarAlpha); } curSlider.draw(sliderBarColor); } for(Checkbox curCheckbox : checkboxes) { curCheckbox.draw(); } } // fade bar out else if(curBarAlpha > 5.0) { interfaceOn = false; curBarAlpha = constrain(curBarAlpha * 0.9, 0, barAlpha); fill(bgColor, curBarAlpha); rect(0, 0, width, barY); } if(indicateWeight) { textAlign(CENTER, CENTER); fill(255); textFont(font, 24); text("Connection Strength: "+nf(weight,0,2), width/2, height/2); if(millis() - indicateWeightTime > 3000) indicateWeight = false; } // Print debug info if(millis()-lastStatusTime > 1000) { //println(numChromas+" chromas, "+nf(frameRate,0,1)+" fps"); lastStatusTime = millis(); } } // Draw all the chromatophers. void drawChromas() { noStroke(); for(int i=numChromas-1; i>=0; i--) { // update youngest first chromas[i].update(); if(toDrawConnections && !useThreading) { if(toDrawConnsLayers[chromas[i].layerNum]) { //chromas[i].drawParentConn(); chromas[i].drawConnections(); } } chromas[i].drawChroma(); //chromas[i].drawChromaCircle(); } } void drawConnectionsThread() { working = true; PGraphics tmpImgConn = createGraphics(width, height); Chroma[] tmpChromas = new Chroma[maxChromas]; arrayCopy(chromas, tmpChromas); // no improvement over tmpChromas = chromas tmpImgConn.beginDraw(); tmpImgConn.noStroke(); tmpImgConn.fill(bgColor, fadeTrans); tmpImgConn.rect(0, 0, width, height); for(int i=numChromas-1; i>=0; i--) { // update youngest first if(toDrawConnsLayers[tmpChromas[i].layerNum]) { //chromas[i].drawParentConn(); tmpChromas[i].drawConnections(tmpImgConn); } } tmpImgConn.endDraw(); imgConn = tmpImgConn; working = false; } /*// expt to see if can continuously update network image in background void everDraw() { working = true; PGraphics tmpImgConn = createGraphics(width, height); //Chroma[] tmpChromas = new Chroma[maxChromas]; //arrayCopy(chromas, tmpChromas); // no improvement over tmpChromas = chromas tmpImgConn.beginDraw(); tmpImgConn.noStroke(); tmpImgConn.fill(bgColor, fadeTrans); tmpImgConn.rect(0, 0, width, height); while(keepDrawing) { working = true; for(int i=numChromas-1; i>=0; i--) { // update youngest first if(toDrawConnsLayers[tmpChromas[i].layerNum]) { //chromas[i].drawParentConn(); tmpChromas[i].drawConnections(tmpImgConn); } } tmpImgConn.endDraw(); working = false; imgConn = tmpImgConn; } }*/ // Spawn a chroma somewhere a chroma is needed, starting search near curChroma. void addChroma() { // calculate the size of the new chroma float diaFactor = 1 + random(-diaJitter, diaJitter); float newMinDia = minDias[curLayer] * diaFactor; float newMaxDia = maxDias[curLayer] * diaFactor; // find new coordinates near an existing chroma Chroma spawnChroma = chromas[curChroma]; float newAng = random(0, 2*PI); float[] newCoord = spawnChroma.angDistToCoord(newAng, interDist[curLayer]); // test if new coord is in a good spot, and if not, try a different spot near spawnChroma int attempts = 0; int tempChromaNum = 0; int searchDirection = 1-int(random(1))*2; // random -1 or 1 int[] nearChromas = new int[0]; while( tempChromaNum < numChromas ) { // skip if we've tried enough with this chroma if(attempts > maxAttempts) { break; } if(debugSpawning) println("Now testing on chroma "+tempChromaNum+", "+(numChromas-tempChromaNum)+" left"); // check if coordinates within viewport //if(newCoord[0] < 0 || newCoord[0] > width || newCoord[1] < 0 || newCoord[1] > height) { if(newCoord[0] < -maxShift || newCoord[0] > width+maxShift || newCoord[1] < -maxShift || newCoord[1] > height+maxShift) { //if(newCoord[0] < newMinDia || newCoord[0] > width-newMinDia || newCoord[1] < newMinDia || newCoord[1] > height-newMinDia) { if(debugSpawning) println("- outside bounds"); newAng += random(PI/4, PI); newCoord = spawnChroma.angDistToCoord(newAng, interDist[curLayer]); tempChromaNum = 0; // nearness testing starts over attempts++; // count attempts for this spawn point continue; } // check if new coordinates are in another chroma's zone if(chromas[tempChromaNum].near(newCoord[0], newCoord[1], interDist[curLayer])) { if(debugSpawning) { println("- near chroma "+tempChromaNum); fill(0, 180); ellipse(newCoord[0], newCoord[1], 10, 10); } // check if tempChroma has already been touched int i=0; boolean alreadyTouched = false; while(i < nearChromas.length) { if(tempChromaNum == nearChromas[i]) { alreadyTouched = true; break; } i++; } // if not in list, record this tempChroma number //if(!alreadyTouched && chromas[tempChromaNum].layerNum == curLayer) // TODO within layer only; maybe more realistic if(!alreadyTouched) nearChromas = append(nearChromas, tempChromaNum); // get new coordinates newAng += searchDirection*random(PI/32, PI/16); newCoord = spawnChroma.angDistToCoord(newAng, interDist[curLayer]); tempChromaNum = 0; // nearness testing starts over attempts++; } else { // not near this chroma, so check the next tempChromaNum++; } } // found some empty space, so drop new chroma if(tempChromaNum == numChromas) { if(debugSpawning) println(numChromas+": "+nf(newCoord[0],0,1)+", "+nf(newCoord[1],0,1)+", "+nf(newMinDia,0,1)); // create new chroma, using list of near chromas as initial list of connections (parent will be added by constructor) chromas[tempChromaNum] = new Chroma(newCoord[0], newCoord[1], curLayer, numChromaSides, newMinDia, newMaxDia, tempChromaNum, spawnChroma.idNum, nearChromas); // bidirectionality: connect all near chromas to this chroma; // also for fun, inject some activity around the spawned chroma for(int nearChroma : nearChromas) { chromas[nearChroma].conns = append(chromas[nearChroma].conns, tempChromaNum); chromas[nearChroma].act += newChromaAct; } // bidirectionality: connect spawn chroma to this chroma if(!spawnChroma.isConn(tempChromaNum)) { spawnChroma.conns = append(spawnChroma.conns, tempChromaNum); } if(debugConnections) {//debugSpawning print("new "+tempChromaNum +" conn: "); for(int c : chromas[tempChromaNum].conns){ print(c+" "); } println(); print("spawn "+spawnChroma.idNum +" conn: "); for(int c : spawnChroma.conns){ print(c+" "); } println("\n"); } spawnChroma.numKids++; // add to parent chroma's number of kids numChromas++; lastChromaAddedT = millis(); } // no empty space found else { if(debugSpawning) print("- can't find a spawn point near chroma "+curChroma); // next attempt spawn from diff chroma curChroma = (curChroma+1) % numChromas; // sequential //curChroma = int(random(0, numChromas)); // random // skip if the temp chroma already has lots of kids while(chromas[curChroma].numKids > 7) { //println("too many mouths to feed on chroma "+curChroma+"!"); curChroma = (curChroma+1) % numChromas; // sequential } // layer-specific spawning parameters if(curLayer > 1) { // TODO param // don't spawn layer 2 from layer 0 while(chromas[curChroma].layerNum == 0) { curChroma = (curChroma+1) % numChromas; // sequential //curChroma = int(random(0, numChromas)); } } if(debugSpawning) println(", next time will try chroma "+curChroma); } } // Spawn a chroma in a certain place. void dropChroma(float x, float y) { float diaFactor = 1 + random(-diaJitter, diaJitter); float newMinDia = minDias[curLayer] * diaFactor; float newMaxDia = maxDias[curLayer] * diaFactor; float[] newCoord = new float[]{x, y}; //println(numChromas+": "+nf(newCoord[0],0,1)+", "+nf(newCoord[1],0,1)+", "+nf(newMinDia,0,1)); chromas[numChromas] = new Chroma(newCoord[0], newCoord[1], curLayer, numChromaSides, newMinDia, newMaxDia, numChromas); numChromas++; lastChromaAddedT = millis(); isAdding = true; } // Create the first chromatophore. void growSeedChroma() { float diaFactor = 1 + random(-diaJitter, diaJitter); float newMinDia = minDias[curLayer] * diaFactor; dropChroma(random(newMinDia, width-newMinDia), random(newMinDia, height-newMinDia)); } // Start over void eraseChromas() { //println("--- building a new patch of skin ---"); numChromas = 0; curChroma = 0; curLayer = 0; chromas = new Chroma[maxChromas]; growSeedChroma(); } void mousePressed() { //println(stimReady); //stimReady = false; /*for(Checkbox curCheckbox : checkboxes) { if(curCheckbox.over()) { if(curCheckbox.name.equals("black")) toDrawConnsLayers[0] = curCheckbox.checked; else if(curCheckbox.name.equals("red")) toDrawConnsLayers[1] = curCheckbox.checked; else if(curCheckbox.name.equals("yellow")) toDrawConnsLayers[2] = curCheckbox.checked; else if(curCheckbox.name.equals("reset")) eraseChromas(); } }*/ for(Checkbox curCheckbox : checkboxes) { if(curCheckbox.over()) { if(curCheckbox.name.equals("black")) toDrawConnsLayers[0] = curCheckbox.checked; else if(curCheckbox.name.equals("red")) toDrawConnsLayers[1] = curCheckbox.checked; else if(curCheckbox.name.equals("yellow")) toDrawConnsLayers[2] = curCheckbox.checked; else if(curCheckbox.name.equals("twitch")) toShift ^= true; else if(curCheckbox.name.equals("reset")) eraseChromas(); } } updateDrawConnectionState(); } void updateDrawConnectionState() { toDrawConnections = false; for(boolean drawConnFlag : toDrawConnsLayers) { if(drawConnFlag){ toDrawConnections = true; break; } } } int overCheckbox() { int over = -1; for(Checkbox curCheckbox : checkboxes) { if(curCheckbox.over()) { over = checkboxes.indexOf(curCheckbox); break; } } return over; } void mouseReleased() { int overChromaNum = overAny(); if(overChromaNum != -1) { curChroma = overChromaNum; chromas[curChroma].act += 1; // little bump //chromas[curChroma].printChroma(1); // restart spawn search lastChromaAddedT = millis(); isAdding = true; } int clickedCheckboxNum = overCheckbox(); if(clickedCheckboxNum != -1) { if(checkboxes.get(clickedCheckboxNum).name.equals("reset")) { checkboxes.get(clickedCheckboxNum).checked = false; } } lastStimTime = 0; stimReady = true; } void keyPressed() { switch(key) { // toggle running case 'r': isRunning ^= true; if(isRunning) loop(); else noLoop(); break; // save network case 's': saveNetwork(); break; // start on next layer case 'n': curLayer = (curLayer+1) % numLayers; //println("now adding layer "+curLayer); lastChromaAddedT = millis(); break; case 'c': toDrawConnections ^= true; break; case 't': useThreading ^= true; //println("threading: "+useThreading); break; case 'w': eraseChromas(); break; case ']': weight += 0.01; //println("weight: "+weight); break; case '[': weight -= 0.01; //println("weight: "+weight); break; case '1': toDrawConnsLayers[0] ^= true; checkboxes.get(0).checked = toDrawConnsLayers[0]; //println("draw connections for layer 1: "+toDrawConnsLayers[0]); break; case '2': toDrawConnsLayers[1] ^= true; checkboxes.get(1).checked = toDrawConnsLayers[1]; //println("draw connections for layer 2: "+toDrawConnsLayers[1]); break; case '3': toDrawConnsLayers[2] ^= true; checkboxes.get(2).checked = toDrawConnsLayers[2]; //println("draw connections for layer 3: "+toDrawConnsLayers[2]); break; case 'l': loadNetwork(networkFileName); break; } updateDrawConnectionState(); } // Wiimote IR sensor void infrared_1(float _x, float _y) { //println(_x+", "+_y); // reversed //wiiX_1 = width - _x*width; wiiX_1 = _x*width; wiiY_1 = height - _y*height; } // Wiimote IR sensor void infrared_3(float _x, float _y) { //println(_x+", "+_y); // reversed //wiiX_3 = width - _x*width; wiiX_3 = _x*width; wiiY_3 = height - _y*height; } void stimButton_1(float val) { //println(val); if(val == 1) { stim1 = true; } else { stim1 = false; } } void stimButton_3(float val) { //println(val); if(val == 1) { stim3 = true; } else { stim3 = false; } } void refractDown(float val) { runningActW -= 0.1; //println("runningActW: "+runningActW); textAlign(CENTER, CENTER); fill(255); //text("Refractory Speed: "+runningActW, width/2, height/2); } void refractUp(float val) { runningActW += 0.1; //println("runningActW: "+runningActW); textAlign(CENTER, CENTER); fill(255); //text("Refractory Speed: "+runningActW, width/2, height/2); } void connectionDown(float val) { weight -= 0.01; indicateWeight = true; indicateWeightTime = millis(); //println("weight: "+weight); textAlign(CENTER, CENTER); fill(255); //text("Connection Strength: "+weight, width/2, height/2); } void connectionUp(float val) { weight += 0.01; indicateWeight = true; indicateWeightTime = millis(); //println("weight: "+weight); //text("Connection Strength: "+weight, width/2, height/2); } // Returns number of chroma under mouse, and -1 if nobody. int overAny() { int overChromaNum = -1; for(int i=0; i0) { if(numConns>0) { chromas[numChromas] = new Chroma(x, y, layer, numSides, minDia, maxDia, id, parentID, connections); } else { chromas[numChromas] = new Chroma(x, y, layer, numSides, minDia, maxDia, id, parentID); } numChromas++; } if(maxLayerNum < layer) maxLayerNum = layer; } isAdding = false; curLayer = maxLayerNum; //println("current layer: "+curLayer); } class Slider { int x, y, w, h, textY; float percent, value, minVal, maxVal, curveFactor; String name; int textOffsetY = 0; Slider(String _name, int _x, int _y, int _w, int _h, float _value, float _minVal, float _maxVal) { this(_name, _x, _y, _w, _h, _value, _minVal, _maxVal, 1.0); } Slider(String _name, int _x, int _y, int _w, int _h, float _value, float _minVal, float _maxVal, float _curve) { name = _name; x = _x; y = _y; w = _w; h = _h; value = _value; minVal = _minVal; maxVal = _maxVal; curveFactor = _curve; percent = getPercent(); textY = y+h+textOffsetY; } void draw(color barColor) { // bar rectMode(CORNER); stroke(barColor); int trans = int((value-minVal)/(maxVal-minVal)*255); fill(barColor, trans); rect(x, y, w, h, 3); // indicator int lineX = int(percent*w + x); strokeWeight(1); stroke(200, 200); line(lineX, y, lineX, y+h); // title textFont(font, 10); textAlign(LEFT, TOP); fill(barColor); text(name, x, textY); // value //fill(30, 90, 150); //fill(sin(PI*trans/255.0)*255); fill(constrain(140-trans, 60, 140)); textFont(font, 16); textAlign(LEFT, CENTER); text(nf(value,0,3), x+4, y+h/2-2); /*if(percent < 0.75) { textAlign(LEFT, CENTER); text(nf(value,0,3), lineX+2, y+h/2); } else { textAlign(RIGHT, CENTER); text(nf(value,0,3), lineX-2, y+h/2); }*/ } float getValue() { percent = (mouseX-x)/(float)w; value = (maxVal-minVal)*pow(percent, curveFactor) + minVal; return value; } float getPercent() { //percent = (value-minVal) / (maxVal-minVal); percent = pow((value - minVal)/(maxVal-minVal), 1.0/curveFactor); //println(percent); return percent; } boolean over() { if(mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) { return true; } else return false; } } class Checkbox { int x, y, w, h, textY, shade; String name; boolean checked; Checkbox(String _name, int _x, int _y, int _w, int _h, boolean _val) { name = _name; x = _x; y = _y; w = _w; h = _h; checked = _val; textY = y+h; } void draw() { if(checked) fill(80, 150); else noFill(); stroke(100, curBarAlpha); rect(x, y, w, h, 3); // name textFont(font, 10); textAlign(LEFT, TOP); fill(100, curBarAlpha); text(name, x, textY); } boolean over() { if(mouseX > x && mouseX < x+w && mouseY > y && mouseY < y+h) { //shade = 120; if(mousePressed) { checked ^= true; } //println(checked); return true; } else { return false; } } } /*----- CHROMA ----------------------------------------------------------------- */ class Chroma { float centerX, centerY; float curX, curY = 0.0; float muscleShiftX, muscleShiftY = 0.0; int layerNum; // layer number this chroma belongs to color c; float minDia, curDia, maxDia; int idNum, parent; int[] conns = new int[0]; //float[] weights; int numSides; float radsPerSide; float[] rads; // vector of angles representing each vertex float[] radsSin; // sin of angles float[] radsCos; float act = 0.0; // default activation when chroma first plops down float inact = 1.0; float[] actHistory = new float[numHistoryTPts]; int actHistoryPtr = 0; int numKids = 0; float runningAct = 0.0; //----- CONSTRUCTORS --------------------------------------------------------- // Chroma(float cX, float cY, int _layerNum, int _numSides, float minD, float maxD, int id) { //this(cX, cY, _layerNum, _numSides, minD, maxD, id, new int[0]); this(cX, cY, _layerNum, _numSides, minD, maxD, id, -1); } // with parent Chroma(float cX, float cY, int _layerNum, int _numSides, float minD, float maxD, int id, int parentID) { this(cX, cY, _layerNum, _numSides, minD, maxD, id, parentID, new int[0]); } // with connections Chroma(float cX, float cY, int _layerNum, int _numSides, float minD, float maxD, int id, int parentID, int[] _conns) { centerX = cX; centerY = cY; layerNum = _layerNum; numSides = _numSides; minDia = minD; maxDia = maxD; curDia = minDia; idNum = id; parent = parentID; conns = _conns; // add parent to list of connections, for bidirectionality if(!this.isConn(parentID) && parentID != -1) // if not connected to parent, and parent is not the genesis chroma (-1) conns = append(conns, parentID); c = colors[layerNum]; radsPerSide = TWO_PI/numSides; randRads(); // IDEA use PShape instead, may be more efficient if the shape does not change, just scales } //----- METHODS -------------------------------------------------------------- // Gets activation of chroma, taking into account the inactivation variable, // the activations of connected chromas, and activation leak. void getAct(){ // adjust inactivation... //inact = min(max(inact+0.0001, inact*1.01), 1.0); // get connection activation... float w = weight; //if(useDistWeight){ // w = getWeight(parent, weight); //} //act += w*chromas[parent].act; // parent is a sep connect at the moment for(int i=0 ; i 0) curDia = sqrt(act)*(maxDia-minDia) + minDia; else curDia = act*(maxDia-minDia) + minDia; // color int maxTrans = 190; float trans = 255 - constrain(maxTrans*(curDia / maxDia), 0, maxTrans); // fade when expanded fill(c, trans); if(idNum == curChroma && debugSpawning) fill(200); stroke(0, 20); /*if(layerNum == 2) { stroke(0, 20); strokeWeight(1); } else { //noFill(); noStroke(); }*/ // muscle twitch //noise(); float[] coords = rads2coords(muscleJitter); // jitter version beginShape(); // POLYGON for(int i=0 ; i0){ print("\n[conns "); for(int conn : conns) print(conn+" "); println("]"); } if(level>1){ println("\t[coords: " + centerX + ", " + centerY + "]"); } } // Plots the activation history near this chromatophore's coordinates void plotHostory() { } // Generate a line of text representing this chromatophore. // id layer parentID x y minDia maxDia "r" numAngles [angles] "c" numConns [connIDs] String getTextLine() { String newLine = idNum+" "+layerNum+" "+parent+" "; // position newLine += centerX+" "+centerY+" "+minDia+" "+maxDia+" "; // angles newLine += "r ";//+numSides+" "; for(int i=0; i 0) { newLine += " c ";//+conns.length+" "; for(int i=0; i 0){ float[] newRs = new float[numSides-1]; int delR = int(random(0, numSides)); // get random int //println("point: " + delR + ", " + rads[delR]); for(int i=0 ; i