xxxxxxxxxx
// Pendulum Wave Effect
// by Steve Kranz, 2015
// stevecrayons@gmail.com
// www.stevecrayons.com
//
// Licensed under Creative Commons 0 Universal 1.0 -- Public Domain. Feel free to do whatever with this code. If you do use it, I'd love to see what you did. Send me a note at stevecrayons@gmail.com
float[] frequencies; // Holds the frequencies of each pendulum
PVector[] positions; // Holds the position of the balls that simulate the pendulum bobs
float amplitude; // How far left and right the balls move
float freqMult = 0.001; // This frequency multiplier makes the system complete a whole period in 1000 frames.
int totalPen; // The total number of pendulums/balls
boolean total20 = true; // Are there 20 balls? If false, there are 40.
boolean prevTotal20 = true; // Is the value of total20 on the previous frame 20? This is for switching between 20 and 40 pendulums.
float xPos, yPos; // x- and y-positions of the pendulums
float xCenter; // For alignig the systems of pendulums in the x-direction
float boxHeight; // The y-height of the invisible box that the balls exist in; for display purposes.
float two_pi = 6.283185307;
// Drawing options
boolean draw1 = true;
boolean draw2 = true;
boolean draw3 = true;
boolean drawBalls = true;
boolean alignSin = true;
// TIME
float timeValue=0;
float timeSpeed=1;
boolean debugOn = false;
// BUTTONS
int clickCount = 0; // so a button is activated only once per click
Button ballButton, numBallsButton, line1Button, line2Button, line3Button, labelButton;
Button pauseButton, speedButton;
void setup(){
size(720, 720);
frameRate(30);
amplitude = width/8;
setupPendulum();
setupButtons();
}
void draw(){
background(40,40,45);
// Buttons...
displayButtons();
displayButtonLabels();
displayTitle();
displayPhaseCircle();
if(debugOn){
debug();
}
calculatePositions();
if(labelButton.state){
drawGuides();
drawLabels();
}
if(line3Button.state){
drawLine(3, color(230, 180, 70));
}
if(line2Button.state){
drawLine(2, color(230, 115, 70));
}
if(line1Button.state){
drawLine(1, color(230, 70, 65));
}
setBobColor();
if(ballButton.state){
// This loop draws the 'bobs'.
for (int i = 0; i<totalPen; i++){
ellipse(positions[i].x, positions[i].y, 18, 18);
}
}
totalPenChange();
incrementTime();
/*
if(frameCount % 2 == 0){
saveFrame("save2color/####.png");
}*/
}// end draw
class Button {
int x, y, w, h;
String label, shortcut;
String onText, offText;
color onColorFore, offColorFore;
color onColorBack, offColorBack;
boolean state;
float labelWidth;
Button(int x_, int y_, int w_, int h_, String label_, String shortcut_){
x = x_;
y = y_;
w = w_;
h = h_;
label = label_;
shortcut = shortcut_;
state = true;
textSize(16);
labelWidth = textWidth(label);
onColorFore = color(255, 200);
onColorBack = color(255, 100);
offColorFore = color(0, 230);
offColorBack = color(100, 180);
onText = "on";
offText = "off";
}
void update(){
if(clickCount == 0){
if(over()){
if(mousePressed==true){
state =! state;
clickCount++;
}
}
}
display();
}
void display(){
textAlign(CENTER, CENTER);
textSize(16);
if(state==true){
noStroke();
fill(onColorBack);
rect(x,y,w,h);
fill(onColorFore);
text(onText, x+w/2, y+h/2);
} else {
noStroke();
fill(offColorBack);
rect(x,y,w,h);
fill(offColorFore);
text(offText, x+w/2, y+h/2);
}
textAlign(LEFT, CENTER);
if(state){
fill(255, 160);
} else {
fill(255, 80);
}
text(label, x+w+6, y + h/2);
textAlign(CENTER, CENTER);
text(shortcut, x-28, y + h/2);
}
boolean over() {
if (mouseX >= x -33 && mouseX <= x + w + 6 + labelWidth && mouseY >= y && mouseY <= y + h)
{
return true;
} else {
return false;
}
}
}
void setBobColor(){
// stroke(10);
noStroke();
fill(255, 255);
strokeWeight(1);
}
// This function draws lines that connect the pendulums
void drawLine(int n, color c){
strokeWeight(4);
stroke(c);
for(int i=0; i<totalPen-n; i++){
line(positions[i].x, positions[i].y, positions[i+n].x, positions[i+n].y);
}
}
// This function allows time to progress at a slow or fast rate, or pause
void incrementTime(){
if(pauseButton.state==false){
if(speedButton.state==false){
timeValue += timeSpeed;
} else{
timeValue += timeSpeed*2;
}
}
}
// This function sets up the frequency and position arrays for the pendulums
void setupPendulum(){
xCenter = 6*width/10 + 85;
boxHeight = height-25;
if(total20){
totalPen = 20;
freqMult = 0.001;
} else {
totalPen = 40;
freqMult = 0.0005;
}
frequencies = new float[totalPen];
positions = new PVector[totalPen];
for (int i = 0; i< totalPen; i++){
frequencies[i] = (i+1) * freqMult;
}
}
// Has the state of the "Number of pendulums" button changed? If so, run the setupPendulum() function
void totalPenChange(){
total20 = numBallsButton.state;
if(total20 == prevTotal20){
setupPendulum();
}
prevTotal20 = total20;
}
// Draw the horizontal lines underneath each of the pendulums
void drawGuides(){
strokeWeight(1);
stroke(255, 30);
for(int i = 0; i<totalPen; i++){
line(xCenter-amplitude, positions[i].y, xCenter+amplitude, positions[i].y);
}
}
// Draw the text labels above and two the right of the pendulums
void drawLabels(){
textSize(12);
fill(255, 70);
for(int i = 0; i<totalPen; i++){
textAlign(CENTER, CENTER);
text(i+1, xCenter+amplitude + 27, positions[i].y);
}
int yOffset;
if(total20){
yOffset = 20;
} else{
yOffset = 15;
}
text("freq.", xCenter+amplitude + 27, yOffset);
text("pendulum", xCenter, yOffset);
}
// Draw the title information in the upper-righthand corner
void displayTitle(){
int x = 110;
int y = 40;
textSize(30);
textAlign(LEFT, TOP);
fill(255, 255);
textLeading(32);
text("Pendulum\nWave Effect", x, y);
fill(255, 0);
text("Pendulum\nWave Effect", x+1, y);
fill(255, 127);
textSize(14);
text("by Steve Kranz, 2015", x, y+67);
textSize(12);
text("stevecrayons@gmail.com",x, y+83 );
}
// setup the buttons used to control the simulation.
void setupButtons(){
int x = 110;
int y = 210;
int w = 40;
int h = 30;
int yGap = 32;
int drawGap = 0;
ballButton = new Button(x,y+yGap*0+drawGap, w, h, "pendulums", "0");
numBallsButton = new Button(x, y+yGap*4+8, w, h, "number of pendulums", "N");
numBallsButton.onText = "20";
numBallsButton.offText = "40";
line1Button = new Button(x, y+yGap*1+drawGap, w, h, "line 1", "1");
line2Button = new Button(x, y+yGap*2+drawGap, w, h, "line 2", "2");
line3Button = new Button(x, y+yGap*3+drawGap, w, h, "line 3", "3");
labelButton = new Button(x, y+yGap*5+drawGap + 8, w, h, "labels", "L");
// labelButton.state = false;
pauseButton = new Button(x, y+yGap*6 + 70, w, h, "pause", "P");
pauseButton.state = false;
speedButton = new Button(x, y+yGap*7 + 70, w, h, "speed", "S");
speedButton.onText = "fast";
speedButton.offText = "slow";
speedButton.state = false;
}
// Draw the labels that annotate the buttons
void displayButtonLabels(){
// x and y should match x and y in setupButtons();
int x = 110;
int y = 210;
textSize(16);
fill(255, 255);
textAlign(LEFT, TOP);
text("Drawing options", x, y-30);
text("Timing options", x, y+232);
textAlign(CENTER, TOP);
textSize(12);
textLeading(11);
fill(255, 100);
text("short\ncut", x-30, y-32);
stroke(255, 70);
strokeWeight(1);
line(x, y-9, x+220, y-9 );
line(x, y+253, x+130, y+253);
}
void displayButtons(){
ballButton.update();
numBallsButton.update();
line1Button.update();
line2Button.update();
line3Button.update();
labelButton.update();
pauseButton.update();
speedButton.update();
}
// This circle simply displays the phase of the system. When the indictor line makes a complete revolution, the pendulum-system repeats itself.
void displayPhaseCircle(){
float x = 163;
float y = 640;
float d = 60;
float angle = timeValue*freqMult*two_pi - two_pi/4;
textSize(16);
textAlign(LEFT, CENTER);
fill(255,255);
text("Phase", 110, y - d/2 -35);
// text("phase", x, y-12);
stroke(255, 70);
strokeWeight(1);
line(110, y - d/2 - 24, 110+105, y - d/2 - 24);
strokeWeight(1);
stroke(255, 100);
fill(255, 30);
ellipseMode(CENTER);
ellipse(x, y, d, d);
// Tick marks
strokeWeight(1);
stroke(255, 100);
float tick = 0.8;
line(x, y-tick*d/2, x, y-d/2); // zero or two-pi (top of circle);
line(x + tick*d/2, y, x+d/2, y); // pi/2
line(x, y+tick*d/2, x, y+d/2); // pi
line(x- tick*d/2, y, x-d/2, y); // 3pi/2
textSize(12);
fill(255, 150);
textAlign(CENTER, CENTER);
text("0", x+1, y -d/2 -10);
// text("π/2", x+d/2 + 15, y-2);
text("π", x, y + d/2 + 8);
//text("3π/2", x-d/2-17, y-2);
// 3pi/2 on two lines so it looks nicer
text("3π", x-d/2-14, y-8);
text("2", x-d/2-14, y+6);
stroke(255, 100);
strokeWeight(1);
line(x-d/2-22, y, x-d/2-6, y);
// pi/2 on two lines so it looks nicer
text("π", x+d/2+12, y-8);
text("2", x+d/2+12, y+6);
stroke(255, 100);
strokeWeight(1);
line(x+d/2+18, y, x+d/2+6, y);
// Indicating line...the vertical spacing will depend on how the font is rendered...can be iffy.
strokeWeight(2);
stroke(255, 200);
line(x, y, x+(d/2)*cos(angle), y+(d/2)*sin(angle));
}
void debug(){
fill(255);
textSize(12);
textAlign(LEFT);
text("FPS: "+nf(frameRate, 2, 1), 12, 15);
text("frame number: "+frameCount, 12, 27);
text("time value: "+nfc(timeValue,1), 12, 39);
text("Hit 'D' to hide.", 12, 51);
}
void calculatePositions(){
// This loop calculates the position of all the 'bobs' (or "balls" or "pendulums") for this current frame
for (int i = 0; i< totalPen; i++){
yPos = (i+1) * ((float)boxHeight/(totalPen+1)) + 25;
if(alignSin==true){
xPos = xCenter + amplitude * sin(frequencies[i]*timeValue*two_pi);
} else {
xPos = xCenter + amplitude * cos(frequencies[i]*timeValue*two_pi);
}
positions[i] = new PVector(xPos, yPos);
}
}
void keyPressed(){
if (key=='1'){
line1Button.state =! line1Button.state;
}
if (key =='2'){
line2Button.state =! line2Button.state;
}
if (key =='3'){
line3Button.state =! line3Button.state;
}
if (key =='`'|| key=='0'){
ballButton.state =! ballButton.state;
}
if (key ==' ' || key == 'p' || key == 'P'){
//paused =! paused;
pauseButton.state =! pauseButton.state;
}
if (key == 's' || key=='S'){
//timeSlow =! timeSlow;
speedButton.state =! speedButton.state;
}
if (key =='n' || key == 'N'){
numBallsButton.state =! numBallsButton.state;
}
if (key == 'l' || key == 'L'){
labelButton.state =! labelButton.state;
}
if(key == 'd' || key == 'D'){
debugOn =! debugOn;
}
if(key =='r' || key == 'R'){
saveFrame();
}
if(key == 'q'|| key == 'Q'){
alignSin =! alignSin;
}
}
void mouseReleased(){
clickCount = 0;
}