/** *

Esfera mod by spxl

*

by subpixel

* *

Based on Esfera.pde by David Pena

* *

v6 - Multicolour! Timewarp!

* *

Mouse:

* *

Keys:

* */ /* * Modifications by subpixel: * v1: * removal of unused global arrays; * calculate end point of a hair as the hairLength distance from the hair base, not as a point at a fixed distance from the centre of the sphere (which causes hairs to stretch); * reverse Ymouse reactivity so ball spins in the direction of mouse movement instead of against it; * added start/pause functionality on mouse click event * v2: 2008-11-09 * renamed/repurposed some identifiers * Globals: cuantos -> NUM_HAIRS, lista[] -> HAIRS[], radio -> BASE_RADIUS, * hairmin/max -> MIN/MAX_HAIR_LENGTH, rx/ry -> RX/RY * class pelo -> class Hair * dibujar() -> draw() * new class Oscillator * Oscillator introduced for the radius of the inner sphere, the base of the "outer sphere" (the hairs), * and also the number of hairs displayed. * The inner sphere is now red so you can see it * Moving the mouse increases the "heart rate" of the inner sphere (which gradually slows down) * Moving the mouse also increases the base radius of the outer sphere based on the speed * v3: 2008-11-17 * Extracted class Hair and class Oscillator to separate files. * Removed redundant FRAME variable (can use global frameCount if necessary) * Added some keyboard controls. * [up]/[down] control outer sphere osciallation step * [z] toggles pause for inner sphere oscillator * [x] toggles pause for outer sphere oscillator * [c] toggles pause for hair count osciallator * Added some 3D text * Added some snaking tentacles; three new osciallators control parameters. * v4: 2008-11-24 * MASSIVE CHANGES! * Move references to outerOscillator.value and millis() out of Hair class * Oscillators now based around a period instead of a phase step. * Oscillator.tick() now requires a parameter for the number of milliseconds that have passed * 8 "Hello" petals replaced by details of available oscillators. * Concept of a "selected" oscillator, which can be manipulated by keyboard input: * Selected oscillator details shown at top-left of window * Selected oscillator hilighted a different coloured "petal" around the inner sphere * [LEFT]/[RIGHT] to select previous/next oscillator * [z],[x],..[m] to directly select an oscillator * [UP]/[DOWN] to increase/decrese the oscillation period * [1]..[9] to directly set the period to 1..9 seconds * [0] to pause * [SPACE] to toggle pause/unpause * [BACKSPACE] to toggle backwards/forwards direction * v5: 2008-12-11 Lights! * v6: 2008-12-18 Multicolour! * Use a specific number of point lights * Oscillate RGB values for color of tentacles * Update keyboard controls: * halve/double period (for current oscillator or all oscillators) * increase/decrease aplitude by 10% * increase/decrease median by 10% * key for square wave changed * Allow object to be moved around with mouse * Zooming * Jump position to mouse * Jump/move position to random lcation * Petals/sphere/tentacles display toggle * Tentacles noise (random variation) * 2009-01-05 Timewarp! (warps number of milliseconds passing per frame) */ import processing.opengl.*; // ------------------------------------------------------------ // Global variables // ------------------------------------------------------------ boolean paused = false; // Opening state of the sketch. Set true to open un-paused. boolean dispInfo = true; // Display information about current oscillator boolean dispSphere = true; // Display the inner sphere boolean dispHairs = true; // Display hairs boolean dispPetals = true; // Display petals boolean dispLights = true; // Display point lights boolean dispTentacles = true; // Display the tentacles boolean dispTTxt = true; // Display tentales text ("binary code") boolean useAgitation = false; // Sphere agitation on mouse move boolean useTNoise = true; // Tentacle noise int numPointLights = 5; // Number of point lights to use ArrayList oscillators = new ArrayList(); // To keep track of all oscillators int numOsc; // Used in place of "oscillators.size()" int clockMillis = 0; // Milliseconds since start of applet to start of current frame int clockPrevMillis = 0; // Milliseconds since start of applet to start of previous frame int runMillis = 0; // Milliseconds we have been running (not including when paused) float timeWarp = 1.0f; // Factor to scale passing time by int FRAME_RATE = 30; // Max desired frame rate (actual may be less) int NUM_HAIRS = 800; // Number of hairs Hair[] HAIRS; // Array/list of hairs float flingAmount = 0; // Amount to fling hairs outwards by from mouse movement) float BASE_RADIUS; // Radius of the sphere float MIN_HAIR_LENGTH; // Minimum hair length float MAX_HAIR_LENGTH; // Maximum hair length float globalZoom; float toZoom; // Sphere origin float ox; float oy; float oz; // Sphere origin "target" float tox; float toy; float toz; float RX = 0; // Rotation float RY = 0; // Rotation float agitation; float maxInnerPeriod; // Slowest sphere will pulsate float minInnerPeriod; // Fastest sphere will pulsate Oscillator innerOsc; // Oscillator for inner sphere Oscillator outerOsc; // Oscillator for outer sphere (base of hairs) Oscillator countOsc; // Oscillator for number of hairs to display Oscillator tentacleCurl; // Oscillator for the tentacle curl amount Oscillator tentacleTwist; // Oscillator for the tentacle twist amount Oscillator tentacleScale; // Oscillator for the scaling of each tentacle. <1 grow smalled, >1 grow larger Oscillator tentacleNoise; // Oscillator for the control of the noise function for the tentacles Oscillator spin; // Phase speed for rotation of the petals/tentacles Oscillator hilightOsc; // Oscillator for positioning the point lights Oscillator tiltOsc; // Oscillator for petal tilt amount Oscillator cRed; // Cube colour: red component Oscillator cGreen; // Cube colour: green component Oscillator cBlue; // Cube colour: blue component Oscillator selectedOsc; // Allow modification of the selected oscillator int selectedOscIndex; PFont font; // ------------------------------------------------------------ // Override PApplets init method // to set the frame to undecorated=true // ------------------------------------------------------------ void init() { // frame.setUndecorated(true); // This doesn't seem to work in Processing 1.0 super.init(); } // ------------------------------------------------------------ // Main setup method // ------------------------------------------------------------ void setup() { size(1200, 480, OPENGL); // Set the canvas size, and use OpenGL rendering // set the location of the frame frame.setLocation(0*screen.width, 0); frameRate(FRAME_RATE); background(0); if (paused) noLoop(); // Opportunity to open the sketch in a paused state // Start with the object centred ox = tox = width / 2; oy = toy = height / 2; oz = toz = -width / 3; globalZoom = toZoom = 1.0; BASE_RADIUS = height/4; // The central sphere is half the window height (radius therefore 1/4 window height) MIN_HAIR_LENGTH = BASE_RADIUS * 0.1; // Minimum hair length set proportionally to the sphere radius MAX_HAIR_LENGTH = BASE_RADIUS * 0.4; // Maximum hair length ser proportionally to the sphere radius // Set up the outer sphere hairs HAIRS = new Hair[NUM_HAIRS]; // Initialise the array for the number of hairs for (int i=0; i < NUM_HAIRS; i++) // For each hair... { float phi = random(TWO_PI); // Any angle in 360 degrees float theta = asin(random(-1, 1)); float hairLength = random(MIN_HAIR_LENGTH, MAX_HAIR_LENGTH); HAIRS[i] = new Hair(phi, theta, hairLength); // Create a new hair object and save it in the array } maxInnerPeriod = 3; minInnerPeriod = 0.5; float b = BASE_RADIUS; oscillators.add(innerOsc = new Oscillator("inner", maxInnerPeriod, 0.2 * b, 0.9 * b)); oscillators.add(outerOsc = new Oscillator("outer", 4, 0.2 * b, 1.2 * b)); oscillators.add(countOsc = new Oscillator("haircount", 5, 0.5 * NUM_HAIRS, 0.5 * NUM_HAIRS)); oscillators.add(tentacleCurl = new Oscillator("curl", 26, PI/12)); oscillators.add(tentacleTwist = new Oscillator("twist", 37, PI/12, 0)); oscillators.add(tentacleScale = new Oscillator("scale", 12, 0.15, 0.9)); oscillators.add(spin = new Oscillator("spin", 128)); // Since there are 8 petals/tentacles oscillators.add(hilightOsc = new Oscillator("hilight", 1.5, 1000, 990)); // Point light moving up and down selected oscillator text petal oscillators.add(tiltOsc = new Oscillator("tilt", 8, PI/4, -PI/4)); // Point light moving up and down selected oscillator text petal oscillators.add(tentacleNoise = new Oscillator("noise", 22, 20, 15)); oscillators.add(cRed = new Oscillator("cRed", 16, 127, 128, 0)); // Cube colour: red component oscillators.add(cGreen = new Oscillator("cGreen", 12, 127, 128, 0)); // Cube colour: green component oscillators.add(cBlue = new Oscillator("cBlue", 22, 127, 128, 0)); // Cube colour: blue component // Start with the first oscillator selected. selectOscillator(0); numOsc = oscillators.size(); // Set number of "octaves" for noise function noiseDetail(3); // Set up the font for drawing text font = loadFont("BroadwayBT-Regular-48.vlw"); textFont(font); // (re)Start the clock! :o) clockMillis = millis(); // Ignore any time passed up til now. } // ------------------------------------------------------------ // Main draw method // ------------------------------------------------------------ void draw() { // Timing calculations clockPrevMillis = clockMillis; clockMillis = millis(); int millisSinceLastFrame = clockMillis - clockPrevMillis; // frameMillis and runMillis are affected by the timeWarp... int frameMillis = int(millisSinceLastFrame * timeWarp); runMillis += frameMillis; // Update the origin float originMoveAmt = 0.2; if (abs(timeWarp) < 1.0) originMoveAmt *= sq(timeWarp); ox = lerp(ox, tox, originMoveAmt); oy = lerp(oy, toy, originMoveAmt); oz = lerp(oz, toz, originMoveAmt); // Update the zoom factor float zoomChangeAmt = 0.1; if (abs(timeWarp) < 1.0) zoomChangeAmt *= sq(timeWarp); globalZoom = lerp(globalZoom, toZoom, zoomChangeAmt); // Update all of the oscillators if (frameCount > 1) for (int i = 0; i < numOsc; i++) ((Oscillator)(oscillators.get(i))).tick(frameMillis); // Clear the display. Black background. lights(); background(0); // Display information about the selected oscillator if (dispInfo) { fill(200, 200, 200); textAlign(LEFT); String message = paused ? "PAUSED" : selectedOsc.str(); text(message, 10, 48, 0); } // Centre the coordinate system translate(ox, oy, oz); // Position the coordinate origin at the centre of the window // Apply zoom factor scale(globalZoom); // Ease towards current mouse "position" (orientation determined by mouse position) float rxp = ((mouseX-(width/2)) * PI / height); float ryp = (((height/2)-mouseY) * PI / height); float rotDist = dist(rxp, ryp, RX, RY); float rotChangeAmt = 0.1; if (abs(timeWarp) < 1.0) rotChangeAmt *= sq(timeWarp); RX = lerp(RX, rxp, rotChangeAmt); RY = lerp(RY, ryp, rotChangeAmt); rotateY(RX); rotateX(RY); // The change in rotation also flings the hairs outwards float flingChangeAmt = 0.25; if (abs(timeWarp) < 1.0) flingChangeAmt *= sq(timeWarp); flingAmount = lerp(flingAmount, rotDist * 40, flingChangeAmt); // Mouse movement causes the inner sphere to be "agitated"... faster heartbeat if (useAgitation) { float mouseMoveDist = dist(mouseX, mouseY, pmouseX, pmouseY); agitation = (agitation + mouseMoveDist * 0.1) * 0.97; innerOsc.setPeriod( minInnerPeriod + (maxInnerPeriod - minInnerPeriod) / ( 1 + agitation ) ); } // Everything is now decided so time to draw the scene if (dispLights) addPointLights(); if (dispSphere) drawSphere(); if (dispHairs) drawHairs(flingAmount); if (dispPetals) drawPetals(); if (dispTentacles) drawTentacles(); } // ------------------------------------------------------------ // Add point lights just in front of the text petals // ------------------------------------------------------------ void addPointLights() { // pushMatrix(); // Falloff = 1 / (p1 + p2 * d + p3 * d^2) lightFalloff(0.001, 0, 0.005); for (int i = 0; i < numPointLights; i += 1) { pushMatrix(); rotateZ(TWO_PI * i / numPointLights + spin.phase); rotateY(tiltOsc.value()); translate(innerOsc.value(), 0, 0); translate(hilightOsc.value(), 0, 5); pointLight(255, 255, 255, 0, 0, 0); popMatrix(); } // popMatrix(); } // ------------------------------------------------------------ // Draw inner sphere // ------------------------------------------------------------ void drawSphere() { // pushMatrix(); fill(127, 0, 0); // 50% Red... noStroke(); // No outline... sphere( innerOsc.value() ); // popMatrix(); } // ------------------------------------------------------------ // Draw outer sphere hairs // ------------------------------------------------------------ void drawHairs(float flingAmount) { // pushMatrix(); int displayHairs = round(countOsc.value()); float baseRadius = outerOsc.value() + flingAmount; for (int i=0; i < displayHairs; i++) // For each hair... { HAIRS[i].draw( baseRadius, runMillis ); // Calculate and draw the hair } // popMatrix(); } // ------------------------------------------------------------ // Draw text Petals // ------------------------------------------------------------ void drawPetals() { // pushMatrix(); for (int i = 0; i < numOsc; i++) { pushMatrix(); // Add text information about the indexed oscillator sticking out from the edge of the sphere. // Tilt it towards the "front" a bit so it is kind of like a flower. float angle = TWO_PI * i / numOsc + spin.phase; rotateZ(angle); rotateY(tiltOsc.value()); translate(innerOsc.value(), 0, -15); if (i == selectedOscIndex) { fill (220, 220, 0); } else { fill(0, 102, 153); } textAlign(LEFT); String message = ((Oscillator)(oscillators.get(i))).str(); text(message, 0, 15, 0); popMatrix(); } // popMatrix(); } // ------------------------------------------------------------ // Draw the tentacles // ------------------------------------------------------------ void drawTentacles() { // pushMatrix(); for (int i = 0; i < numOsc; i++) { pushMatrix(); // Remember base matrix of the sphere so we can come back for the next tentacle // Add a tentacle made up of a sequence of relatively sized/positioned/oriented boxes float angle = TWO_PI * i / numOsc - spin.phase; rotateZ(angle); translate(outerOsc.value(), 0, 0); int numSegments = 15; float opacity = 255; for (int segment = 0; segment < numSegments; segment++) { fill(cRed.value(), cGreen.value(), cBlue.value(), opacity); noStroke(); // No outline... pushMatrix(); rotateX(PI * 0.25); // Make the tentacles diagonal to the plane that their base is on box(40); // "Binary code" displayed on one side of the box if (dispTTxt) { fill(255,255,0); int altSeg = segment + (int)(spin.value() * 10); rotateX(HALF_PI * (floor(2 * noise(altSeg * 7731) - 1))); String txt = (noise(segment, i, altSeg * 10) < 0.5) ? "0" : "1"; text(txt, -20, 20, 21); } popMatrix(); // Move along a bit if (useTNoise) { translate(tentacleScale.value() * (tentacleNoise.median + noise(segment, i, tentacleNoise.phase) * (2 * tentacleNoise.amplitude - 1)), 0, 0); // Move along a bit } else { translate(tentacleScale.value() * (tentacleNoise.median + tentacleNoise.amplitude), 0, 0); } rotateZ(tentacleScale.value() * tentacleCurl.value()); // Curl around a bit rotateX(tentacleScale.value() * tentacleTwist.value()); // Twist a bit scale(tentacleScale.value()); // Scale each segment relative to the last (may be bigger or smaller depending on oscialltor) opacity *= 0.95; // Make each segment less opaque than the last } popMatrix(); // Go back to base matrix of the sphere for the next tentacle } // popMatrix(); } // ------------------------------------------------------------ // Handle mouse input // Set the origin based on mouse position when button pressed // (including click and drag) // ------------------------------------------------------------ void mousePressed() { originMoveToMouse(); } void mouseDragged() { originMoveToMouse(); } // ------------------------------------------------------------ // Handle keyboard input // ------------------------------------------------------------ void keyPressed() { if (key == CODED) { println(keyCode); if (keyCode == 19) { togglePaused(); } // Pause/Break key else if (keyCode == LEFT) { selectPrevOscillator(); } else if (keyCode == RIGHT) { selectNextOscillator(); } else if (keyCode == UP) { setSelectedPeriod(selectedOsc.period + 0.1); } else if (keyCode == DOWN) { setSelectedPeriod(selectedOsc.period - 0.1); } else if (keyCode == 112) { dispInfo = !dispInfo; } // F1 toggle display of current oscillator information else if (keyCode == 113) { dispPetals = !dispPetals; } // F2 toggle display of text petals else if (keyCode == 114) { dispTTxt = !dispTTxt; } // F3 toggle display of "binary code" on tentacles } else if (key == 'y') { timeWarp *= 0.25f; } else if (key == 'u') { timeWarp *= 2.0f; } else if (key == 'i') { timeWarp *= -1f; } else if (key == 's') { dispSphere = !dispSphere; } else if (key == 'S') { useAgitation = !useAgitation; } else if (key == 'a') { useAgitation = !useAgitation; } else if (key == 'A') { useAgitation = false; } else if (key == 'h') { dispHairs = !dispHairs; } else if (key == 'p') { dispPetals = !dispPetals; } else if (key == 'P') { dispPetals = true; } else if (key == 't') { dispTentacles = !dispTentacles; } else if (key == 'T') { useTNoise = !useTNoise; } else if (key == 'l') { dispLights = !dispLights; } else if (key == 'L') { dispLights = true; } else if (key == ',') { selectedOsc.waveShape = 0; } // Sine else if (key == '.') { selectedOsc.waveShape = 1; } // Triangle else if (key == '/') { selectedOsc.waveShape = 2; } // Sawtooth else if (key == '?') { selectedOsc.waveShape = 3; } // Square else if (key == ' ') { selectedOsc.togglePause(); } else if (key == '-') { selectedOsc.adjustPeriod(0.5); } else if (key == '=') { selectedOsc.adjustPeriod(2.0); } else if (key == BACKSPACE) { selectedOsc.reverseDirection(); } else if (key == '_') { halveAllOscPeriods(); } else if (key == '+') { doubleAllOscPeriods(); } else if (key == 'z') { selectOscillator(0); } else if (key == 'x') { selectOscillator(1); } else if (key == 'c') { selectOscillator(2); } else if (key == 'v') { selectOscillator(3); } else if (key == 'b') { selectOscillator(4); } else if (key == 'n') { selectOscillator(5); } else if (key == 'm') { selectOscillator(6); } else if (key == '[') { selectedOsc.amplitude *= 0.90909; } else if (key == ']') { selectedOsc.amplitude *= 1.1; } else if (key == '{') { selectedOsc.median *= 0.90909; } else if (key == '}') { selectedOsc.median *= 1.1; } else if (key == '0') { selectedOsc.togglePause(); } else if (key == ')') { selectedOsc.pause(); } else if (key == '1') { setSelectedPeriod(1); } else if (key == '2') { setSelectedPeriod(2); } else if (key == '3') { setSelectedPeriod(3); } else if (key == '4') { setSelectedPeriod(4); } else if (key == '5') { setSelectedPeriod(5); } else if (key == '6') { setSelectedPeriod(6); } else if (key == '7') { setSelectedPeriod(7); } else if (key == '8') { setSelectedPeriod(8); } else if (key == '9') { setSelectedPeriod(9); } else if (key == 'j') { originJumpToMouse(); } // Jump origin to mouse location else if (key == 'o') { originJumpTo(rnd(width), rnd(height)); } // Jump origin to random location else if (key == 'O') { originMoveTo(rnd(width), rnd(height)); } // Move origin to random location else if (key == 'q') { zoomTo(mouseY * 3.0 / height); } } // ------------------------------------------------------------ // Toggle paused state // ------------------------------------------------------------ void togglePaused() { paused = !paused; if (paused) { // Pause the main loop noLoop(); } else { // (Re-)start the main loop loop(); clockMillis = millis(); // Ignore any time passed up til now. } } // ------------------------------------------------------------ // Select previous oscillator // ------------------------------------------------------------ void selectPrevOscillator() { selectOscillator( (selectedOscIndex - 1 + numOsc) % numOsc ); } // ------------------------------------------------------------ // Select next oscillator // ------------------------------------------------------------ void selectNextOscillator() { selectOscillator( (selectedOscIndex + 1) % numOsc ); } // ------------------------------------------------------------ // Start move / jump to x,y coordinates based on mouse location (Z coordinate unchanged) // ------------------------------------------------------------ void originMoveToMouse() { originMoveTo(mouseX, mouseY); } void originJumpToMouse() { originJumpTo(mouseX, mouseY); } // ------------------------------------------------------------ // Hard jump to x,y coordinates based on screen location (Z coordinate unchanged) // ------------------------------------------------------------ void originJumpTo(int x, int y) { originMoveTo(x, y); ox = tox; oy = toy; } // ------------------------------------------------------------ // Start move to x,y coordinates based on screen location (Z coordinate unchanged) // ------------------------------------------------------------ void originMoveTo(int x, int y) { int w2 = width / 2; int h2 = height / 2; tox = w2 + (x - w2) * 2; toy = h2 + (y - h2) * 2; } // ------------------------------------------------------------ // Start global zoom (scale) to passed zoomFactor // ------------------------------------------------------------ void zoomTo(float zoomFactor) { toZoom = zoomFactor; } // ------------------------------------------------------------ // Select the the numbered oscillator // ------------------------------------------------------------ void selectOscillator(int i) { selectedOscIndex = constrain(i, 0, oscillators.size() - 1); selectedOsc = (Oscillator)(oscillators.get(selectedOscIndex)); } // ------------------------------------------------------------ // Set period of currently selected oscillator // ------------------------------------------------------------ void setSelectedPeriod(float period) { selectedOsc.setPeriod(period); selectedOsc.unPause(); } // ------------------------------------------------------------ // Adjust period of selected oscillator // ------------------------------------------------------------ void doubleSelectedOscPeriod() { selectedOsc.adjustPeriod(2.0f); } void halveSelectedOscPeriod() { selectedOsc.adjustPeriod(0.5f); } // ------------------------------------------------------------ // Adjust periods of all oscialltors // ------------------------------------------------------------ void increaseAllOscPeriods() { adjustAllOscPeriods(1.1f); } void decreaseAllOscPeriods() { adjustAllOscPeriods(0.9090909f); } void doubleAllOscPeriods() { adjustAllOscPeriods(2.0f); } void halveAllOscPeriods() { adjustAllOscPeriods(0.5f); } void adjustAllOscPeriods(float factor) { Oscillator o; for (int i = 0; i < numOsc; i++) { o = (Oscillator)(oscillators.get(i)); o.adjustPeriod(factor); } } // ------------------------------------------------------------ // 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) { if (value < minValue || value > maxValue) { int rangeSize = maxValue - minValue + 1; for (;value < minValue; value += rangeSize); for (;value > maxValue; value -= rangeSize); } return value; }