/** *

Radial Gradient Array

*

by subpixel

* *

What was a simple array of filled circles is now... dynamic! :o)

*

Keys:

* * *

Try pplaying some music and pressing O/P on fairly regular beats, * D/F/T on less often beats, and R on more dramatic changes in the music.

* *

Maybe it's just me, but I got sucked into watching this for a while... * mind you, I've spent ages twilddling the knobs behind the scenes, so it's * been (in some sense) good value. :o)

* *

Have a peek in the code for some exciting calculations for the sizes of * the circles and the spacing...

*
k = 1/2 * sqrt( pi/c ) - 1
 * D = x / ( n + k(n+1) )
* *

Based on Simple Radial Gradient by Ira Greenberg.
* Examples > Basics > Color > RadialGradient

*/ // Flag to determine whether to use the set() function to plot pixels or the // loadPixels() and updatePixels() functions with the pixels[] array boolean usePixelsArray = true; // The number of different "patterns" (ways of choosing which grid position to fill) static final int NUM_FILL_PATTERNS = 7; int fillPattern = 0; // The current fill pattern int prevPattern = 0; // The previous fill pattern int design = 0; // The current design (for fill pattern 5) int designStep = 0; // What step we're up to the in the current design boolean createNewDesign = true; // Need to initialise designs[0] first time used // In the designs, the following letters are for directional movement: // q w e // a s d - (s is stationary, so draw over the same place) // z x c // UPPER case means leave a trail, lower case means don't leave a trail // Digits 2-9 are used as a modifier (n) to say move n places in specified direction; no digit means n=1 // r means move randomly (up to n places in any direction) String designs[] = { "", // Used for a generated design! "DDWWAAWWDDdDDXXAAXX3dWEQW2dXZCX3dAAWWWW5c9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r9r", // SPXL "5dDW2A2X3D3W4A4XDDDDDWWWWWAAAAAAXXXXXXDDDDDDDWWWWWWWAAAAAAAAXXXXXXXXDDDDDDDDDDCXZaazxxcxzzxccdd", // spiral "XXDDWWAAWWDDDDXXXDXXDDWWAAWWDDDDXXXDXXDDWWAAWWDDDDXXXD3r", "ddddxxxxddddww", "ssssAdDaWxXw4deeecccczzzcczzqqqz", "4D4z4W2e3e2e", "XXXrdDDd3RddDDD5R5R5R5R5R5ReeeDDDeeeDDDeeR2r2r2r2r2r2r2r2rdddZZZZrEE", "eeedcccdeeedccccdeeedcccdeeedcccceeddeeccczzzzzqqaazzxxcccdd" }; // BASIC PARAMETERS float coverage; // Density of coverage over one "box" (eg .6 means 60% the area is covered by the circles) int divisions; // The number of rows or columns in the grid (actually the number in the longest dimension, the shorter dimension will have less) boolean antiAlias = false; // Smooth the circle edges? boolean paused = false; // Pause the sketch? boolean trailMode = false; // Join each circle to the previous circle? float maxTrailJump = 4; // The number of grid spaces that can be spanned by a single trail int trailCount = 0; // Temporarily activate/suppress trails for how many (more) frames? int trailShotDuration = 40; // Number of frames to activate/suppress trails per "shot" int baseFadeAmount = 2; // Amount to fade by ("ambient" fade) for every frame int maxBaseFadeAmount = 20; // Maximum base fade amount selectable by user input boolean fadeMode = false; // Fade effect active? int fadeAmount = 15; // Additional amount to fade by when fade effect active (toggled or fade shot) int fadeCount = 0; // Temporarily activate/suppress fade for how many (more) frames? int fadeShotDuration = 25; // Number of frames to activate/suppress fade effect per "shot" // CALCULATED VALUES float divSize; // The height (and width) of each column int cols; // The number of columns int rows; // The number of rows float diameter; // The circle diameter float radius; // The circle radius // Offsets to centre the rows and columns int colOffset; int rowOffset; // Working variables useful for debug float spaceFactor; float totalSpace; float spaceSize; // STATE VARIABLES // The previous center color color prevColor; // If we want to update the display after drawing each gradient, we need to // keep track of where we are up to when initially filing the grid. // Location of next gradient to draw int col = 0; int row = 0; // Keep track of which way we're going for some pattern(s) int goLeftRight = 1; // -1 for left, +1 for right int goUpDown = 1; // -1 for up, +1 for down // Previous circle's centre's pixel coordinates int prevx = 0; int prevy = 0; // Count of circles filled since last reset() int countSinceReset; // ------------------------------------------------------------ // Initial setup. // ------------------------------------------------------------ void setup(){ size(640, 480); background(0); smooth(); randomiseSettings(); prevColor = rndColor(); reset(); } // ------------------------------------------------------------ // Handle keyboard input. // ------------------------------------------------------------ void keyPressed() { // Some changes require recalculation of values and clearing the display. boolean doReset = false; if (key == CODED) { println("keyCode: " + keyCode); if (keyCode == UP) // Increase coverage { coverage += (coverage < 0.1) ? 0.01 : 0.1; // Step is smaller when value is smaller doReset = true; } else if (keyCode == DOWN && coverage > 0.01) // Decrease coverage { coverage -= (coverage <= 0.11) ? 0.01 : 0.1; // Step is smaller when value is smaller doReset = true; } else if (keyCode == LEFT && divisions > 1) // Reduce number of rows/columns { divisions--; doReset = true; } else if (keyCode == RIGHT) // Increase number of rows/columns { divisions++; doReset = true; } } else { println("key: [" + key + "]"); if (key == ' ') // SPACE to pause / unpause the draw looping { paused = !paused; if (paused) noLoop(); else loop(); } else if (key == '`') // Use pixels[] array (or not) toggle { usePixelsArray = !usePixelsArray; println("usePixelsArray: " + usePixelsArray); } else if (key == '~') // Debug information { debugInfo(); } else if (key == 'a' || key == 'A') // Antialias toggle { antiAlias = !antiAlias; } else if (key == 'd') // Pick a design { setFillPattern(5); // Pick a different design design = rndNotX(designs.length, design); designStep = 0; } else if (key == 'D') // Repick the previous design { setFillPattern(5); } else if (key == 'c') // Create new random design { setFillPattern(6); createNewDesign = true; } else if (key == 'C') // Go back to last design created { setFillPattern(6); } else if (key == 'p' || key == 'P') // Random fill pattern { setFillPattern(rndNotX(NUM_FILL_PATTERNS, fillPattern)); } else if (key == 'o' || key == 'O') // Next fill pattern (in order) { setFillPattern(wrapConstrain(fillPattern + 1, 0, NUM_FILL_PATTERNS - 1)); } else if (key == 'g' || key == 'G') // Trail toggle { trailMode = !trailMode; } else if (key == 't' || key == 'T') // Trail shot { trailCount = trailShotDuration; } else if (key == 'v' || key == 'V') // Fade toggle { fadeMode = !fadeMode; } else if (key == 'f' || key == 'F') // Fade shot { fadeCount = fadeShotDuration; } else if (key == '[') // Decrease base fade amount { if (baseFadeAmount > 0) baseFadeAmount--; } else if (key == ']') // Increase base fade amount { if (baseFadeAmount < maxBaseFadeAmount) baseFadeAmount++; } else if (key == '{') // Minimise base fade amount { baseFadeAmount = 0; } else if (key == '}') // Maximise base fade amount { baseFadeAmount = maxBaseFadeAmount; } else if (key == 'r' || key == 'R') // Randomise settings { randomiseSettings(); } } if (doReset) reset(); } // ------------------------------------------------------------ // Select some random settings for various parameters and // reset the grid. // ------------------------------------------------------------ void randomiseSettings() { divisions = rnd(20) + 1; coverage = 0.1 * (rnd(8) + 1); baseFadeAmount = rnd(maxBaseFadeAmount / 4); // Don't make base fade too strong fadeMode = random(1) < 0.5; trailMode = random(1) < 0.5; setFillPattern(rnd(NUM_FILL_PATTERNS)); reset(); } void setFillPattern(int newPattern) { prevPattern = fillPattern; fillPattern = newPattern; } // ------------------------------------------------------------ // Clear the display, recalculate all the important values. // ------------------------------------------------------------ void reset() { background(0); countSinceReset = 0; int maxSize; // Will be width or height if (divisions == 1) maxSize = min(width, height); else if (width > height) maxSize = width; else maxSize = height; // Ratio of the gaps between discs to the disc diameter. // Calculate such that the area of the circle divided by the area of the // square made joining adjacent circle centres is equal to the coverage ratio. // If L is the length of the sides of the square, and r is the radius of the circle // then // A = L^2 is the area of the square // a = pi * r^2 is the area of the circle // If c is the coverage ratio, then // a = cA // So // A = a / c // L^2 = (pi * r^2) / c // L = sqrt ( pi * r^2 / c ) // = r * sqrt( pi / c ) // The size of the space (at least in the case where circle is smaller than the square) // we can see is the square side length less the circle's diameter // s = L - 2r // = r * sqrt( pi/c ) - 2r // The ratio of the space to the circle diameter then is // k = s / 2r // = [ r * sqrt( pi/c ) - 2r ] / 2r // = 1/2 * sqrt( pi/c ) - 1 spaceFactor = sqrt( PI / coverage ) * 0.5 - 1; // Want to have the circles evenly spaced to the edges (of the controlling dimension) // x = nD + (n+1)kD // = D( n + k(n+1) ) // So // D = x / ( n + k(n+1) ) // where // x is the dimension we are filling // n is the number of circles // D is the diameter of each circle // k is the ratio of the spaces between the circles to the circle diameter; ie s = kD diameter = float(maxSize) / (divisions + (divisions + 1) * spaceFactor); spaceSize = spaceFactor * diameter; divSize = diameter + spaceSize; radius = diameter / 2; // Number of rows and columns that should fit in the display // (don't always fit when the coverage is large-ish) cols = floor( width / divSize ); rows = floor( height / divSize ); // Top left corner offest to centre the rows and columns on the display colOffset = int( (width - cols * divSize + divSize) / 2 ); rowOffset = int( (height - rows * divSize + divSize) / 2 ); // Make sure the current row and column are inside the new grid size col = constrain(col, 0, cols - 1); row = constrain(row, 0, rows - 1); // No "previous" fill pattern on reset (or at least not matchable) prevPattern = -1; // Reset the design step designStep = 0; debugInfo(); } // ------------------------------------------------------------ // Display informatino about various parameters, calculated // values and state information. // ------------------------------------------------------------ void debugInfo() { println("--------------------------"); println("coverage: " + coverage); println("spaceFactor: " + spaceFactor); println("diameter: " + diameter); println("spaceSize: " + spaceSize); println(""); println("divSize: " + divSize + ", divSize^2: " + sq(divSize)); println("radius: " + radius + ", pi*radius^2: " + PI*sq(radius)); println("ratio: " + PI*sq(radius) / sq(divSize)); println(""); println("Base fade amount: " + baseFadeAmount); println("fade/Count: " + fadeMode + " / " + fadeCount); println("trail/count: " + trailMode + " / " + trailCount); println("fillPattern: " + fillPattern); println("design (step/len): " + design + " (" + designStep + "/" + designs[design].length() + ") [" + designs[design] + "]"); println("antiAlias: " + antiAlias); } // ------------------------------------------------------------ // MAIN DRAW 'LOOP' // The circle to fill has already been decided. // Create the gradient and decide what circle to fill next. // ------------------------------------------------------------ void draw() { // Determine the location of the centre of the circle to be filled. int x = int( colOffset + divSize * col ); int y = int( rowOffset + divSize * row ); // ---------------------------------------------- // Fade the existing content? // ---------------------------------------------- int fadeAlpha = baseFadeAmount; if (fadeMode || fadeCount > 0) { // Don't fade if we're in fade mode and there is a fade count // (ie the fade count suppresses the fade) if (!(fadeMode && fadeCount > 0)) fadeAlpha += fadeAmount; if (fadeCount > 0) --fadeCount; } if (fadeAlpha > 0) { noStroke(); fill(0, fadeAlpha); quad(0, 0, width, 0, width, height, 0, height); } // ---------------------------------------------- // Connect the new circle to the previous circle? // ---------------------------------------------- if (trailMode || trailCount > 0) { // Don't show trail if we're in trail mode and there is a trail count // (ie the trail count supresses the trail) if (!(trailMode && trailCount > 0)) { // Don't draw a connection if we've just reset or the jump is too large if (countSinceReset > 0 && dist(x, y, prevx, prevy) <= divSize * maxTrailJump) { stroke(prevColor); strokeWeight(2); line(prevx, prevy, x, y); } } if (trailCount > 0) --trailCount; } // ---------------------------------------------- // Draw a circle filled with a radial gradient. // ---------------------------------------------- int newColor = rndColor(); createGradient(x, y, int(radius), newColor, prevColor, antiAlias); countSinceReset++; // ---------------------------------------------- // Decide what grid position to fill next. // ---------------------------------------------- if (fillPattern == 0) { // Left-to-right, top-to-bottom (across then down) if (++col >= cols) { col = 0; if (++row >= rows) row = 0; } } else if (fillPattern == 1) { // Random grid position col = rnd(cols); row = rnd(rows); } else if (fillPattern == 2) { // Random walk into one of 8 neighbours (or stay still) col = rndIncDec(col, 0, cols - 1); row = rndIncDec(row, 0, rows - 1); } else if (fillPattern == 3) { // Top-to-bottom, left-to-right (down then across) if (++row >= rows) { row = 0; if (++col >= cols) col = 0; } } else if (fillPattern == 4) { // Zigzag: across then up/down int newCol; col += goLeftRight; if (col < 0 || col >= cols) { goLeftRight = -goLeftRight; col += goLeftRight; // ie go back to same column row += goUpDown; if (row < 0 || row >= rows) { goUpDown = -goUpDown; row += 2 * goUpDown; // ie go to the row in the opposite direction } } } else if (fillPattern == 5 || fillPattern == 6) { // Move according to a design if (fillPattern == 6) design = 0; // Randomly created design if (design == 0 && createNewDesign == true) { designs[0] = randomDesign(); designStep = 0; createNewDesign = false; } int n = 1; // Number of places to move in the specified direction (or random) String d = designs[design]; int len = d.length(); if (designStep >= len) designStep = 0; char inst = d.charAt(designStep); if ('1' < inst && inst <= '9') { n = Integer.parseInt(Character.toString(inst)); designStep = wrapConstrain(designStep + 1, 0, len - 1); inst = d.charAt(designStep); } designStep = wrapConstrain(designStep + 1, 0, len - 1); switch (Character.toLowerCase(inst)) { case 'q': go(-n, -n); break; case 'w': go( 0, -n); break; case 'e': go( n, -n); break; case 'a': go(-n, 0); break; case 's': go( 0, 0); break; case 'd': go( n, 0); break; case 'z': go(-n, n); break; case 'x': go( 0, n); break; case 'c': go( n, n); break; case 'r': go(rnd(n*2+1)-n, rnd(n*2+1)-n); break; // Random n places up/down/left/right default: break; } trailMode = Character.isUpperCase(inst); } // ---------------------------------------------- // Keep track of what we're up to for next time. // ---------------------------------------------- prevx = x; prevy = y; prevColor = newColor; prevPattern = fillPattern; } // ------------------------------------------------------------ // Generate (and return) a random design string // ------------------------------------------------------------ String randomDesign() { final String dirs = "qwedcxza"; // The 8 directions int dir = rnd(8); // Which direction char c = dirs.charAt(dir); // Command String des = ""; int numSteps = rnd(50) + 5; boolean trail = random(1) < 0.5; for (int s = 0; s < numSteps; s++) { // Decide which way to go int r = rnd(100); if (r < 25) ; else if (r < 45) c = dirs.charAt(dir = wrapConstrain(dir - 1, 0, 7)); else if (r < 65) c = dirs.charAt(dir = wrapConstrain(dir + 1, 0, 7)); else if (r < 75) c = dirs.charAt(dir = wrapConstrain(dir - 2, 0, 7)); else if (r < 85) c = dirs.charAt(dir = wrapConstrain(dir + 2, 0, 7)); else if (r < 90) c = dirs.charAt(dir = wrapConstrain(dir + 3, 0, 7)); else if (r < 95) c = dirs.charAt(dir = wrapConstrain(dir + 3, 0, 7)); else if (r < 97) c = 'r'; // random else c = 's'; // stationary // Decide whether to take a single step or a longer step if (random(1) < 0.05 && c != 's') des = des + str(rnd(3) + 1); // Decide whether to toggle leaving a trail if (random(1) < 0.1) trail = !trail; if (trail) c = Character.toUpperCase(c); else c = Character.toLowerCase(c); // Go one step in the current direction? des = des + c; } println("Random design: [" + des + "]"); return des; } // ------------------------------------------------------------ // Randomly incremenr or decrement (or stay the same) from a // given value, within the passed range minValue to maxValue // (inclusive). // ------------------------------------------------------------ int rndIncDec(int n, int minValue, int maxValue) { n = n + rnd(3) - 1; if (n < minValue) n = maxValue; if (n > maxValue) n = minValue; return n; } // ------------------------------------------------------------ // Move (relative) to current column and row // ------------------------------------------------------------ void go(int dx, int dy) { col = wrapConstrain(col + dx, 0, cols - 1); row = wrapConstrain(row + dy, 0, rows - 1); } // ------------------------------------------------------------ // Plot a circle with a radial gradient fill. // ------------------------------------------------------------ void createGradient (int ox, int oy, int radius, color centerColor, color edgeColor, boolean smoothEdge) { // Grab a copy of the screen pixels so we can play with them // Supposedly faster than using multiple calls to set(x,y,c), // but this will be dependant on how many points are plotted. if (usePixelsArray) loadPixels(); // Start at the edge of the circle and work towards the centre // Colour the centre pixel after the main loop to avoid inner loop for (int r = radius; r > 0; r--) { // Trial-and-error optimised angleStep to avoid "holes" is (0.4225 / r) float angleStep = 0.4225 / r; color c = lerpColor(centerColor, edgeColor, float(r) / radius); for (float angle=0; angle < TWO_PI; angle += angleStep) { int x = int( ox + r * cos(angle) ); int y = int( oy + r * sin(angle) ); // Make sure we don't try to access a pixel that is outside the pixel array! if (x >= 0 && x < width && y >= 0 && y < height) { if (usePixelsArray) pixels[x + y * width] = c; else set(x, y, c); } } } // Colour the centre of the circle with color c1 //set(ox, oy, centerColor); if (ox >= 0 && ox < width && oy >= 0 && oy < height) { if (usePixelsArray) pixels[ox + oy * width] = centerColor; else set(ox, oy, centerColor); } // Update the display with the modified pixels[] array if (usePixelsArray) updatePixels(); if (smoothEdge) { // Create a smooth edge; hack anti-aliasing noFill(); stroke(0); strokeWeight(3); ellipse(ox, oy, radius*2, radius*2); } } // ------------------------------------------------------------ // Return a random colour. // ------------------------------------------------------------ color rndColor() { return color(rnd(256), rnd(256), rnd(256)); } // ------------------------------------------------------------ // Return a random integer from 0 to n (not including n) // that is also not equal to x. // ------------------------------------------------------------ int rndNotX(int n, int x) { int val = int(random(n - 1)); return (val < x) ? val : val + 1; } // ------------------------------------------------------------ // Return a random integer from 0 to n (not including n). // ------------------------------------------------------------ int rnd(int n) { return int(random(n)); } // ------------------------------------------------------------ // "Wrap" a value within the limits of minValue and maxValue (inclusive). // Similar to performing modulo arithmetic. // ------------------------------------------------------------ int wrapConstrain(int value, int minValue, int maxValue) { int rangeSize = maxValue - minValue + 1; while (value < minValue) value += rangeSize; while (value > maxValue) value -= rangeSize; return value; }