/**
*
Esfera. David Pena.
*
* Distribucion aleatoria uniforme sobre la superficie de una esfera.
*
* Modifications by subpixel (v4).
* subpixels.com
*
* Keys:
*
* - [LEFT]/[RIGHT] or [z]..[m] to select parameter;
* - [UP]/[DOWN] or [1]..[9] to adjust period;
* - [0] to pause ([SPACE] to toggle pause);
* - [BACKSPACE] to reverse direction;
* - [,][.][/][-] to set wave shape (sin, triangle, sawtooth, square);
*
*/
/*
* 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
*/
import processing.opengl.*;
// ------------------------------------------------------------
// Global variables
// ------------------------------------------------------------
boolean looping = false; // Opening state of the sketch. Set true to open un-paused.
ArrayList oscillators = new ArrayList();
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.
int FRAME_RATE = 30;
int NUM_HAIRS = 800; // Number of hairs
Hair[] HAIRS; // Array/list of hairs
float BASE_RADIUS; // Radius of the sphere
float MIN_HAIR_LENGTH; // Minimum hair length
float MAX_HAIR_LENGTH; // Maximum hair length
float RX = 0;
float RY = 0;
float agitation;
float maxInnerPeriod; // Slowest sphere will pulsate
float minInnerPeriod; // Fastest sphere will pulsate
Oscillator innerOscillator; // Oscillator for inner sphere
Oscillator outerOscillator; // Oscillator for outer sphere (base of hairs)
Oscillator countOscillator; // 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 spin; // Phase ssed for rotation of the petals/tentacles
Oscillator selectedOscillator; // Allow modification of the selected oscillator
int selectedOscillatorIndex;
PFont font;
void init()
{
// frame.setUndecorated(true);
super.init();
}
// ------------------------------------------------------------
// Main setup method.
// ------------------------------------------------------------
void setup()
{
size(640, 480, OPENGL); // Set the canvas size, and use OpenGL rendering
frameRate(FRAME_RATE);
background(0);
if (!looping) noLoop(); // Opportunity to open the sketch in a paused state
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
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(innerOscillator = new Oscillator("inner", maxInnerPeriod, 0.2 * b, 0.9 * b));
oscillators.add(outerOscillator = new Oscillator("outer", 4, 0.2 * b, 1.2 * b));
oscillators.add(countOscillator = new Oscillator("haircount", 5, 0.5 * b, 0.5 * b));
oscillators.add(tentacleCurl = new Oscillator("curl", 6, PI/12));
oscillators.add(tentacleTwist = new Oscillator("twist", 7, PI/12, 0, PI/4));
oscillators.add(tentacleScale = new Oscillator("scale", 9, 0.15, 0.9, -TWO_PI/3));
oscillators.add(spin = new Oscillator("spin", 8)); // Since there are 8 petals/tentacles
// Start with the first oscillator selected.
selectOscillator(0);
noiseDetail(3);
font = loadFont("BroadwayBT-Regular-48.vlw");
textFont(font);
clockMillis = millis(); // Ignore any time passed up til now.
}
// ------------------------------------------------------------
// Main draw method.
// ------------------------------------------------------------
void draw()
{
clockPrevMillis = clockMillis;
clockMillis = millis();
int millisSinceLastFrame = clockMillis - clockPrevMillis;
runMillis += millisSinceLastFrame;
int numOsc = oscillators.size();
// Update all of the oscillators
if (frameCount > 1)
for (int i = 0; i < numOsc; i++)
((Oscillator)(oscillators.get(i))).tick(millisSinceLastFrame);
// Clear the display. Black background.
background(0);
// Display information about the selected oscillator
fill(200, 200, 200);
textAlign(LEFT);
String message = looping ? selectedOscillator.str() : "Click mouse to start";
text(message, 10, 48, 0);
// Centre the coordinate system
translate(width/2, height/2, -width/4); // Position the coordinate origin at the centre of the window
// Ease towards current mouse "position" (orientation determined by mouse position)... "step 10% of the current difference each frame
float rxp = ((mouseX-(width/2))*0.005);
float ryp = (((height/2)-mouseY)*0.005);
float rotDist = dist(rxp, ryp, RX, RY);
RX = (RX*0.9)+(rxp*0.1);
RY = (RY*0.9)+(ryp*0.1);
rotateY(RX);
rotateX(RY);
// Mouse movement causes the inner sphere to be "agitated"... faster heartbeat
float mouseMoveDist = dist(mouseX, mouseY, pmouseX, pmouseY);
agitation = (agitation + mouseMoveDist * 0.1) * 0.97;
innerOscillator.setPeriod( minInnerPeriod + (maxInnerPeriod - minInnerPeriod) / ( 1 + agitation ) );
// Inner sphere
fill(64, 0, 0); // 25% Red...
noStroke(); // No outline...
sphere( innerOscillator.value() ); // Draw the sphere (to occlude hairs on the side away from the viewpoint)
// Outer sphere (hairs)
int displayHairs = round(countOscillator.value());
float baseRadius = outerOscillator.value() + rotDist * 40;
for (int i=0; i < displayHairs; i++) // For each hair...
{
HAIRS[i].draw( baseRadius, runMillis ); // Calculate and draw the hair
}
// Petals/tentacles
for (int i = 0; i < numOsc; i++)
{
float angle = TWO_PI * i / numOsc;
pushMatrix(); // Remember base matrix of the sphere so we can come back for the next text/tentacle
// 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.
pushMatrix(); // Remember state so we can go back and insert a tentacle
rotateZ(angle + spin.phase);
translate(innerOscillator.value(), 0, 0);
rotateY(-PI/6);
fill(0, 102, 153);
if (i == selectedOscillatorIndex) fill (220, 220, 0);
textAlign(LEFT);
message = ((Oscillator)(oscillators.get(i))).str();
text(message, 0, 15, 0);
popMatrix();
// Add a tentacle made up of a sequence of relatively sized/positioned/oriented boxes
rotateZ(angle - spin.phase);
translate(innerOscillator.value(), 0, 0);
float opacity = 200;
for (int j = 0; j < 15; j++)
{
translate(20, 0, 0); // Move along a bit
rotateZ(tentacleCurl.value()); // Curl around a bit
rotateX(tentacleTwist.value()); // Twist a bit
scale(tentacleScale.value()); // Scale each segment relative to the last (may be bigger or smaller depending on oscialltor)
fill(200, 200, 0, opacity); // Colour is yellowish, alpha set to "opacity"
noStroke(); // No outline...
box(40);
opacity *= 0.7; // Make each segment less opaque than the last
}
popMatrix(); // Go back to base matrix of the sphere for the next text/tentacle
}
}
// ------------------------------------------------------------
// Start / stop (pause) the main loop each time a mouse
// button is pressed.
// ------------------------------------------------------------
void mousePressed()
{
if (looping)
{
// Pause the main loop
noLoop();
looping = false;
}
else
{
// (Re-)start the main loop
loop();
looping = true;
clockMillis = millis(); // Ignore any time passed up til now.
}
}
// ------------------------------------------------------------
// Handle keyboard input
// ------------------------------------------------------------
void keyPressed()
{
if (key == CODED)
{
if (keyCode == LEFT || keyCode == RIGHT)
{
selectOscillator( (selectedOscillatorIndex + (keyCode == RIGHT ? 1 : oscillators.size() - 1)) % oscillators.size() );
}
else if (keyCode == UP) { setSelectedPeriod(selectedOscillator.period + 0.1); }
else if (keyCode == DOWN) { setSelectedPeriod(selectedOscillator.period - 0.1); }
}
else if (key == BACKSPACE) { selectedOscillator.reverseDirection(); }
else if (key == ' ') { selectedOscillator.togglePause(); }
else if (key == ',') { selectedOscillator.waveShape = 0; }
else if (key == '.') { selectedOscillator.waveShape = 1; }
else if (key == '/') { selectedOscillator.waveShape = 2; }
else if (key == '-') { selectedOscillator.waveShape = 3; }
else if (key == '0') { selectedOscillator.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 == '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); }
}
// ------------------------------------------------------------
// Helper function to change the currently selected oscillator
// ------------------------------------------------------------
void selectOscillator(int i)
{
selectedOscillatorIndex = constrain(i, 0, oscillators.size() - 1);
selectedOscillator = (Oscillator)(oscillators.get(selectedOscillatorIndex));
}
void setSelectedPeriod(float period)
{
selectedOscillator.setPeriod(period);
selectedOscillator.unPause();
}