Your browser does not support the canvas tag.

previous        Show / Hide Source        Download        next
/* A program that uses masking, reflection, transformation and basic physics to 
   create a simple kaleidoscope effect. Mouse clicks change the x and y values
   of the ball within the center box and reflect that change in the larger background
   pattern.
   
   @author: Marie Abigail Meriel
   @date: October 3, 2015
*/

//so many global variablessssssssss
boolean fillShape = false; //random value that indicates if a shape is filled or not
color strokeColor; //collection of rgb values
int r, g, b;  //random values used to choose color
int x1, x2, x3, x4; //vertices used to build random shapes
int y1, y2, y3, y4;
PGraphics triangle, pattern; //triangle = mask, pattern = pattern to be cut
PImage mirrorHorizontal, mirrorVertical; //used to mirror canvas
PImage patternImage, patternImageBase; //images of the pattern made
float x = 400; //origin x and y where background pattern is positioned
float y = 488; 
float speed = 5; //speed at which ball moves
float gravity = 1; //increases speed as it goes down; mocks gravity
boolean applyPhysics = false; // boolean to determine whether the ball has been moved
int bounceTracker = 0; //used to track bounces to stop endless bouncing
int radian = 0; //used to determine the degree at which the pattern is rotated
boolean messageDismissed = false; //used to determine if message has been seen or not

void setup() {
  size(800, 800);
  //create the mask
  triangle = createGraphics(width, height, JAVA2D);
  triangle.beginDraw();
  triangle.background(0);
  triangle.fill(255);
  triangle.triangle(400, 400, 800, 400, 800, 0);
  triangle.endDraw();

  //create patternImage and snapshot images
  createPattern();
  patternImageBase = pattern.get(); //picture of the original image
  patternImage = pattern.get(); //cut image
  patternImage.mask(triangle.get());
}

void draw() {
  pattern.pushMatrix();
  pattern.imageMode(CENTER);
  pattern.beginDraw();
  //position the image relative to the ball in the middle
  pattern.translate((x - 250) * 4, (y - 300) * 4);
  //radians are defined by key presses
  pattern.rotate(radians(radian));
  pattern.image(patternImageBase, 0, 0);
  pattern.endDraw();
  pattern.popMatrix();

  patternImage = pattern.get(); // get the pattern image
  patternImage.mask(triangle.get()); //cut the pattern according to the mask image
  image(patternImage, 0, 0);

  //show the pattern
  image(patternImage.get(), 0, 0);

  //reflect the cut pattern
  pushMatrix();
  translate(800, 800);
  scale(-1, 1);
  rotate(radians(-90));
  image(patternImage.get(), 0, 0);
  popMatrix();

  //mirror the canvas horizontally
  mirrorHorizontal = get(400, 0, 400, 400);
  translate(400, 0);
  scale(-1, 1);
  image(mirrorHorizontal, 0, 0);

  //mirror the canvas vertically
  mirrorVertical = get(0, 0, 800, 400);
  translate(-400, 800);
  scale(1, -1);
  image(mirrorVertical, 0, 0);

  //draw the miniature pattern to show where x/y is
  drawRectangle();

  //draw ball
  //translating because it appears on the top part of the box
  pushMatrix();
  translate(400, 400);
  scale(-1, -1);
  translate(-400, -400);
    drawEllipse();
  popMatrix();

  //if ball is in the air
  if (applyPhysics) {
    pattern.clear(); //clear the canvas to stop repetitive drawings
    y += speed;
    speed += gravity;
    if (y > 488) {
      speed *= -0.5; //if it hits the bottom of the box, change direction
      y += speed;
      bounceTracker++; //add to the bounce tracker
      if (bounceTracker > 5) { //stop endless bouncing
        speed = 0;
        gravity = 0;
        bounceTracker = 0;
        applyPhysics = false;
        resetValues();
      }
    } else if (y < 312) { //if it hits the top of the box, change direction
      speed *= 0.5;
      y += speed;
    }
  }
  
  //if the message hasn't been dismissed, display the message
  if (!messageDismissed){
    messageDisplay();
  }
}

//color generator
void setStrokeColor() {
  r = int(random(256));
  g = int(random(256));
  b = int(random(256));
  strokeColor = color(r, g, b);
}

//dictates if a shape is to be filled or not
void setFillShape() {
  int i = int(random(2));
  if (i == 1) {
    fillShape = true;
  } else {
    fillShape = false;
  }
}

void drawTriangles() {
  //draw between 15 - 20 shapes
  int x = int(random(15, 20));

  for (int i = 0; i < x; i++) {
    setStrokeColor(); //create a new color
    pattern.stroke(strokeColor); //set the new color to be the stroke color
    pattern.strokeWeight(5);

    //if the value of fillShape is true, set it to the same
    //rgb values set in setStrokeColor
    setFillShape();
    if (fillShape) {
      pattern.fill(strokeColor);
    } else {
      pattern.noFill();
    }

    //y is used to determine if the triangle is going to be upside down or not
    int y = int(random(2));
    
    //let the starting point be anywhere within the canvas
    x1 = int(random(800));
    y1 = int(random(800));
    x2 = x1 - int(random(90, 150)); //triangles are no more than 300 in width
    x3 = x1 + int(random(90, 150));

    //if y == 1, set the triangle right side up
    //else, turn it upside down
    if (y == 1) {
      y2 = y1 + int(random(90, 150));
      y3 = y1 + int(random(90, 150));
    } else {
      y2 = y1 - int(random(90, 150));
      y3 = y1 - int(random(90, 150));
    }

    //draw triangle with random vertices
    pattern.triangle(x1, y1, x2, y2, x3, y3);
  }
}

//draw random circles
void drawCircles() {
  //draw between 15 - 20 circles
  int x = int(random(15, 20));

  for (int i = 0; i < x; i++) {
    setStrokeColor(); //create a new color
    pattern.stroke(strokeColor); //set the color as the stroke color
    pattern.strokeWeight(5);
    
    //determine if the shape is to be filled; if it is, use the same color
    //as created in stroke color
    setFillShape();
    if (fillShape) {
      pattern.fill(strokeColor);
    } else {
      pattern.noFill();
    }

    //let the circle center be anywhere within the canvas
    x1 = int(random(800));
    y1 = int(random(800));
    x2 = int(random(50, 150));//let variable x2 define height and width

    pattern.ellipse(x1, y1, x2, x2);
  }
}

//draw random diamonds
void drawDiamonds() {
  //draw between 15 and 20 diamonds
  int x = int(random(15, 20));

  for (int i = 0; i < x; i++) {
    setStrokeColor(); //create a new color
    pattern.stroke(strokeColor); //set the color to be stroke color
    pattern.strokeWeight(5);

    //Determine if shape is to be filled or not; if it is, use the same color
    //created in setStrokeColor()
    setFillShape();
    if (fillShape) {
      pattern.fill(strokeColor);
    } else {
      pattern.noFill();
    }

    //let the diamond starting point be anywhere within the canvas
    x1 = int(random(800));
    y1 = int(random(800));
    x2 = int(random(50, 150));//variable x2 is used to define height and width
    
    //in order to rotate, the shape must first be translated
    //which takes in the center of the shape
    //so the first point is added to half the height and width to find its center
    x3 = x1 + (x2/2);

    //rotate shape
    pattern.pushMatrix();
    pattern.translate(x3, x3);
    pattern.rotate(radians(45));
    pattern.translate(x3 * -1, x3 * -1);
      pattern.rect(x1, y1, x2, x2);
    pattern.popMatrix();
  }
}

//create the background pattern
void createPattern() {
  pattern = createGraphics(width, height, JAVA2D);
  pattern.beginDraw();
  pattern.background(0);
  //Create the pattern
  drawTriangles();
  drawCircles();
  drawDiamonds();
  pattern.endDraw();
}

//draw transparent black square with transparent pattern image
//the background pattern rotates, but not the black square
void drawRectangle() {
  noStroke();
  fill(0, 200);
  pushMatrix();
  rect(300, 300, 200, 200);
  translate(400, 400);
  rotate(radians(radian));
  translate(-400, -400);
  tint(255, 100); //half the opacity of the image
  image(patternImageBase, 300, 300, 200, 200);
  tint(255, 255); //switch the opacity back to full
  popMatrix();
}

//draw ball
void drawEllipse() {
  fill(255);
  ellipse(x, y, 25, 25);
}

//if true, the ball's position has been moved
void mousePressed() {
  //if the message has been dismissed, execute
  if (messageDismissed) {
    //if the mouse has been clicked within the center box
    if (((mouseX > 312) && (mouseX < 488)) && ((mouseY > 312) && (mouseY < 488))) {
      //apply physics to the ball and change the ball's x/y co-ordinates
      applyPhysics = true;
      x = mouseX;
      y = mouseY;
    }
  } else { //if the message hasn't been dismissed
    if (((mouseX > 200) && (mouseX < 600)) && ((mouseY > 200) && (mouseY < 600))){
      messageDismissed = true;
    }
  }
}

//on event rotate the pattern image
void keyPressed() {
  pattern.clear();
  if (key == CODED) {
    if (keyCode == RIGHT) {
      radian += 45;
    } else if (keyCode == LEFT) {
      radian -= 45;
    }
  }
}

//reset speed and gravity values back to their original settings
void resetValues() {
  speed = 5;
  gravity = 1;
}

//show the initial message that describes the kaleidoscope functionality
void messageDisplay() {
  fill(0);
  rect(100, 100, 600, 600, 50);
  pushMatrix();
  translate(650, 565); //center on which to flip
  scale(-1, -1); //flip it horizontally and vertically
  textSize(24);
  textAlign(CENTER); //make it look pretty
  fill(255);
    //set String objects to hold description
    //\n doesn't put the text in a new line :'(
    String description1 = "This is a kaleidoscope!";
    String description2 = "In the middle of the kaleidoscope is a black square " + 
      "and the background pattern. ";
    String description3 = "Change the pattern in the kaleidoscope by changing the " + 
      "position of the ball.";
    String description4 = "Use the left and right arrow keys to rotate the pattern.";
    String description5 = "Click anywhere in this box to dismiss this message.";
    
    //
    text(description1, 0, 0, 500, 30);
    text(description2, 0, 40, 500, 90);
    text(description3, 0, 120, 500, 90);
    text(description4, 0, 200, 500, 90);
    fill(255, 0, 0);
    text(description5, 0, 275, 500, 90);
  popMatrix();
}