Transform a square into a circle

edited December 2013 in How To...

Hello.

I'm trying to achieve a simple animation where a square gradually morphs into a square.

My initial attempt began with using the rect() function and increasing the border-radius property. However, unlike with CSS it seems you cannot seem to use border-radius to fully convert a square into a circle; there seems to be an upper limit to what the border-radius can be beyond which no value has any effect.

Is this right? And if so, are there any other techniques I might try?

Many thanks in advance for any help!

ben

Answers

  • There was a recent topic about approximating a circle with Bézier curves. By doing this, then by playing with the parameters, you can do the morphing.

  • You could also draw it by parts - 4 arcs and a few lines/rectangles.

  • It looks like you're right about rect(). Here's a test:

    void setup() {
      size(400, 400);
      rectMode(CENTER);
    }
    
    void draw() {
      background(204);
      float round = map(mouseX, 0, width, 0, 200);
      rect(200, 200, 200, 200, round);
      line(200, 0, 200, height);
    }
    

    This should be an Issue at GitHub, it probably should go all the way to a circle as you expected.

  • jwojwo
    edited December 2013

    One way of doing it is to project from the centre of the square and circle in regular angular increments. The circular position is easily found [cx+r.cos(angle), cy+r.sin(angle)]. The square position just involves extending the radius to get one coordinate and fixing it to get the other depending on which of the four quadrants are being projected. For any given angle you can then just lerp() between the circle and square projections.

    The best thing about doing this is that it gives me chance to use the word 'squarcle'.

    Here's a working example:

    void setup()
    {
      size(400, 400);
    }
    
    void draw()
    {
      background(255);
      fill(128);
      noStroke();
    
      drawSquarcle(map(mouseX,0,width,0,1));
    }
    
    
    // t should be between 0 (square) and 1 (circle)
    void drawSquarcle(float t)
    {
      // Define position and size of shape.
      float cx = width/2;
      float cy = height/2;
      float circleRadius = 150;
      float circleX, circleY, squareX, squareY;
    
      float incAngle = radians(1);    // Resolution of shape.
    
      beginShape();
    
      for (float angle=0; angle<TWO_PI; angle +=incAngle)
      {
        circleX = cx + circleRadius*cos(angle);
        circleY = cy + circleRadius*sin(angle);
    
        if ((angle > radians(225)) && (angle <= radians(315)))  // top side
        { 
          float squareRadius = circleRadius / sin(angle);
          squareX = cx - squareRadius*cos(angle);
          squareY = cy-circleRadius;
        }
        else if ((angle > radians(45)) && (angle <= radians(135)))  // Bottom side
        { 
          float squareRadius = circleRadius / sin(angle);
          squareX = cx + squareRadius*cos(angle);
          squareY = cy+circleRadius;
        }
        else if ((angle > radians(135)) && (angle <= radians(225)))  // Left side
        { 
          float squareRadius = circleRadius / cos(angle);
          squareX = cx - circleRadius;
          squareY = cy - squareRadius*sin(angle);
        }
        else // Right side
        {
          float squareRadius = circleRadius / cos(angle);
          squareX = cx + circleRadius;
          squareY = cy + squareRadius*sin(angle);
        }
        vertex(lerp(squareX,circleX,t),lerp(squareY,circleY,t));
      }
      endShape();
    }
    
  • edited November 2014

    Darn, I am too late..................

    and I didn't use the word drawSquarcle which is worse

    // import java.*;
    //
    ArrayList<Ball> balls;
    final int ballWidth = 12; 
    PVector [] rectsPoints = new PVector [ 360 ];
    int time; 
    float speed = 1000.0;
    //
    // ---------------------------------------------------------------
    //
    void setup()
    {
      // init
      size(800, 600);
      // frameRate(3);
      // Create an empty ArrayList
      balls = new  ArrayList();
    
      balls.clear();
      int j=0;
      //we define a rect and put it into an array of PVector
      // ------
      int magicNumber=90;
      int magicNumber2=90*4;
    
      int magicX=220;
      int magicY=120;
    
      for (int i = 0 ; i<magicNumber; i++) {
        rectsPoints[j] = new PVector(magicX, i*4+magicY);
        j++;
      }
    
    
    
      for (int i = 0 ; i<magicNumber; i++) {
        rectsPoints[j] = new PVector(magicX+magicNumber2, i*4+magicY);
        j++;
      }
      for (int i = 0 ; i<magicNumber; i++) {
        rectsPoints[j] = new PVector(i*4+magicX, magicY+magicNumber2);
        j++;
      }
      // ------
      for (int i = 0 ; i<magicNumber; i++) {
        rectsPoints[j] = new PVector(i*4+magicX, magicY);
        j++;
      }
    
      randomize  (rectsPoints); 
    
      //  Collections.shuffle(Arrays.asList(rectsPoints));
    
    
    
      // ------
      // we define a circle   
      for (int i = 0 ; i<360; i++) {
        float r = 200; 
        float x = r * cos ( radians(i-90) ) + width / 2 ;
        float y = r * sin ( radians(i-90) ) + height / 2 ; 
        balls.add (new Ball(  x, y, 
        rectsPoints[i].x, rectsPoints[i].y, 
        6, color (255, 2, 2) ) );  
        //point(x, y);
        //point(rectsPoints[i].x, rectsPoints[i].y);
      }
      rectsPoints=null; 
      //  exit();
    } // func 
    //
    //
    void draw() 
    { 
      background(255);
      //  noLoop(); 
      Ball ball;
    
    
      for (int i=0; i < balls.size(); i++) {
        ball = balls.get(i);
        float x = lerp(ball.x, ball.targetx, time/speed);
        float y = lerp(ball.y, ball.targety, time/speed);
        fill(ball.myColor); 
        //stroke(0,life);
        ellipse(x, y, ball.w, ball.w);
        // point(x, y);
        // ball.move();
        // ball.display();
      }
      //  for (int i=0; i < balls.size(); i++) {
      //    ball = balls.get(i);
      //    if (ball.finished()) {
      //      balls.remove(i);
      //    }
      //  }
    
      if (time<speed)
        time++;
    } // func 
    //
    
    
    // =====================================================================
    
    void keyPressed() {
      time =0;
    }
    
    //void mouseReleased() {
    //  // A new ball object is added to the ArrayList (by default to the end)
    //  balls.add(new Ball(mouseX, mouseY, ballWidth, 
    //  color (random(0, 255), random(0, 255), random(0, 255))));
    //}
    
    
    // =====================================================================
    
    //method for randomizing
    void randomize (PVector[] arrMy) {
    
      for (int k=0; k < arrMy.length-1; k++) {
        // Goal: swap the value at pos k with a rnd value at pos x.
        // Make rnd index x
        int x = (int)random(0, arrMy.length);    
        // swap pos k and x
        arrMy = swapValues(arrMy, k, x);
      } // for
    
      // display(a, 39);
      // return arrMy;
    } // func
    
    
    PVector[] swapValues (PVector[] myArray, int a, int b) {
      // Goal: swap the value at pos a with a value at pos b in
      // Array myArray, return the changed array. 
      // save current value from pos/index a into temp
      PVector temp=myArray[a].get() ;
      // overwrite value at current pos a with value at index b
      println (a+"   "+b);
      myArray[a].set(myArray[b].get());
      // finish swapping by giving the old value at pos a to the
      // pos b.
      myArray[b]=temp.get();
      // return the changed array
      return myArray;
    } // func
    //
    
    // =====================================================================
    // Simple bouncing ball class
    
    class Ball {
      float x;
      float y;
      float targetx;
      float targety;
      color myColor; 
      float speed;
      float gravity;
      float w;
      float life = 255;
    
      Ball(float tempX, float tempY, 
      float tempX2, float tempY2, 
      float tempW, color tempmyColor1 ) {
        x = tempX;
        y = tempY;
        w = tempW;
    
        targetx=tempX2;
        targety=tempY2;
    
        myColor=tempmyColor1; 
    
        speed = 0;
        gravity = 0.1;
      }
    
      void move() {
        // Add gravity to speed
        speed = speed + gravity;
        // Add speed to y location
        y = y + speed;
        // If ball reaches the bottom
        // Reverse speed
        if (y >= height-19) {
          // Dampening
          speed = speed * -0.8;
          y = height-19;
        }
      }
    
      boolean finished() {
        // Balls fade out
        life--;
        if (life < 0) {
          return true;
        } 
        else {
          return false;
        }
      }
    
      void display() {
        // Display the ball
        //fill(0, life);
        fill(myColor); 
        //stroke(0,life);
        ellipse(x, y, w, w);
      }
    } // class   
    
    // =====================================================================
    
  • edited December 2013

    @jwo

    besides:

     Collections.shuffle(Arrays.asList(myArray)); 
    

    how can I use this? He can't find Collections.

    What do I have to import? java util?

    And how can I get my result back into myArray?

    Or is it there automatically?

    Thanks.

  • First approach, based from code I link to in http://forum.processing.org/two/discussion/1797/how-to-use-bezier-curve-to-approximate-one-quarter-of-a-circle

    // Ratio for perfect circle
    float pcr = 0.5522847498;
    float f = 1;
    
    void setup()
    {
      size(550, 550);
      smooth();
    }
    
    void draw()
    {
      background(222);
    
      fill(0x8800EE00);
      stroke(#2288FF);
      drawCircle(width / 2, height / 2, 250);
      if (f < 1.9)
      {
        f *= 1.01;
      }
    }
    
    void drawCircle(float x, float y, float r)
    {
      float l = (1 - pcr * f) * r;
      float d = 2 * r;
      // Assume here default CENTER mode
      x -= r; y -= r;
    
      beginShape();
      vertex(x, y + r); // Left
      bezierVertex(x, y + l,
          x + l, y,
          x + r, y); // Top
      bezierVertex(x + d - l, y,
          x + d, y + l,
          x + d, y + r); // Right
      bezierVertex(x + d, y + d - l,
          x + d - l, y + d,
          x + r, y + d); // Bottom
      bezierVertex(x + l, y + d,
          x, y + d - l,
          x, y + r); // Back to left
      endShape();
    }
    
  • @Chrisir:

    yep util:

    // to be specific..
    import java.util.Collections;
    import java.util.Arrays;
    
    // won't work with primitives types, so Integer
    Integer[] arr = new Integer[10]; 
    
    for (int i = 0; i < arr.length; i++) { 
      arr[i] = i;
    } 
    
    Collections.shuffle(Arrays.asList(arr)); 
    
    println(arr);  
    
  • edited December 2013

    @ _vk: thanks a lot!

    That should make my code much shorter.....

  • edited December 2013

    Processing's newest structure IntList got a shuffle() method too! ;))
    No need to import anything and no wrapper types like Integer either! :P

    http://processing.org/reference/IntList.html
    http://processing.org/reference/IntList_shuffle_.html

  • cool viz, Chrisir! got to digest it!

  • @ GoToLoop

    my array is of type PVector, not int

    @ oat

    Thank you!

    I didn't really use the class, so some re-writing needs to be done here.

    When you get rid of the random and adjust circle angle to the rect, we should get straight lines from circle to rect.

    Chrisir

  • my array is of type PVector, not int.

    I was referring to @_vk's example: ;;)

    // won't work with primitives types, so Integer
    Integer[] arr = new Integer[10]; 
    
  • edited December 2013

    clearer version...

    the values that go into the ArrayList balls are the start and stop of their flight

    (not the position which gets calculated per lerp in display() )

    //
    import java.util.Collections;
    import java.util.Arrays;
    
    //
    ArrayList<Ball> balls;
    int time; 
    float speed = 1000.0;
    
    //
    // ---------------------------------------------------------------
    //
    void setup()
    {
      // init
      size(800, 600);
      // frameRate(3);
      // Create an empty ArrayList
      balls = new  ArrayList();
    
      // balls.clear();
      int j=0;
      //we define a rect and put it into an array of PVector
      // ------
      int magicNumber=90;
      int magicNumber2=90*4;
    
      int magicX=220;
      int magicY=120;
      PVector [] rectsPoints = new PVector [ 362 ];
    
      for (int i = 1; i<magicNumber+1; i++) {  // x const, y moves
        rectsPoints[j] = new PVector(magicX, i*4+magicY);
        j++;
      }
      for (int i = 0 ; i<magicNumber; i++) {  // x const, y moves
        rectsPoints[j] = new PVector(magicX+magicNumber2, (i+0)*4+magicY);
        j++;
      }
      for (int i = 0 ; i<magicNumber+1; i++) {
        rectsPoints[j] = new PVector((i+0)*4+magicX, magicY+magicNumber2);
        j++;
      }
      // ------
      for (int i = 0 ; i<magicNumber+1; i++) {
        rectsPoints[j] = new PVector(i*4+magicX, magicY);
        j++;
      }
    
      println (rectsPoints);
      // randomize rectsPoints 
      // Collections.shuffle(Arrays.asList(rectsPoints)); 
      println (rectsPoints);
    
      // ------
      // we define a circle   
      for (int i = 0 ; i<362; i++) {
        float r = 200; 
        float x = r * cos ( radians(i-0) ) + width / 2 ;
        float y = r * sin ( radians(i-0) ) + height / 2 ; 
        // and put both together into our ArrayList balls 
        balls.add (new Ball(  x, y, 
        rectsPoints[i].x, rectsPoints[i].y, 
        3, 
        color (255, 2, 2) ) );
      }
      rectsPoints=null;
    } // func 
    //
    void draw() 
    { 
      background(255);
      Ball ball;
      for (int i=0; i < balls.size(); i++) {
        ball = balls.get(i);
        ball.display();
      }
      if (time<speed)
        time++;
    } // func 
    //
    
    
    // =====================================================================
    
    void keyPressed() {
      time = 0;
    }
    
    // =====================================================================
    // Simple bouncing ball class
    
    class Ball {
      float startx;
      float starty;
      float targetx;
      float targety;
      color myColor; 
      // float speed;
      //float gravity;
      float w;
      //float life = 255;
    
      Ball(float tempstartX, float tempstartY, 
      float tempX2, float tempY2, 
      float tempW, color tempmyColor1 ) {
        startx = tempstartX;
        starty = tempstartY;
        w = tempW;
    
        targetx=tempX2;
        targety=tempY2;
    
        myColor=tempmyColor1;
      }
    
      void display() {
        fill(myColor); 
        //noStroke();
        float x1 = lerp(startx, targetx, time/speed);
        float y1 = lerp(starty, targety, time/speed);
        ellipse(x1, y1, w, w);
      }
    } // class   
    
    // =====================================================================
    
  • and 3D..............

    //
    import java.util.Collections;
    import java.util.Arrays;
    
    //
    ArrayList<Ball> balls;
    int time; 
    float speed = 1000.0;
    
    // cam stuff
    float camX=0, camY=445.0, camZ= 0;  // its position 
    float camAngle=0;   // rotation 
    float camr = 400;   // radius 
    
    //
    // ---------------------------------------------------------------
    //
    void setup()
    {
      // init
      size(800, 600, OPENGL);
      // frameRate(3);
      // Create an empty ArrayList
      balls = new  ArrayList();
    
      // balls.clear();
      int j=0;
      float j2 = -4;
      //we define a rect and put it into an array of PVector
      // ------
      int magicNumber=90;
      int magicNumber2=90*4;
    
      int magicX=220;
      int magicY=120;
      PVector [] rectsPoints = new PVector [ 362 ];
    
      for (int i = 1; i<magicNumber+1; i++) {  // x const, y moves
        rectsPoints[j] = new PVector(magicX, i*4+magicY, j2);
        j++;
      }
      for (int i = 0 ; i<magicNumber; i++) {  // x const, y moves
        rectsPoints[j] = new PVector(magicX+magicNumber2, (i+0)*4+magicY, j2);
        j++;
      }
      for (int i = 0 ; i<magicNumber+1; i++) {
        rectsPoints[j] = new PVector((i+0)*4+magicX, magicY+magicNumber2, j2);
        j++;
      }
      // ------
      for (int i = 0 ; i<magicNumber+1; i++) {
        rectsPoints[j] = new PVector(i*4+magicX, magicY, j2);
        j++;
      }
    
      println (rectsPoints);
      // randomize rectsPoints 
      Collections.shuffle(Arrays.asList(rectsPoints)); 
      println (rectsPoints);
    
      // ------
      // we define a circle   
      for (int i = 0 ; i<362; i++) {
        float r = 200; 
        float x = r * cos ( radians(i-0) ) + width / 2 ;
        float y = r * sin ( radians(i-0) ) + height / 2 ;
        float z = 8;
        // and put both together into our ArrayList balls 
        balls.add (new Ball(  x, y, z, 
        rectsPoints[i].x, rectsPoints[i].y, rectsPoints[i].z, 
        8, 
        color (255, 2, 2) ) );
      }
      rectsPoints=null;
      sphereDetail(11);
    } // func 
    //
    void draw() 
    { 
      background(255);
      camX= camr * cos ( radians(camAngle) ) +330 ;
      camZ= camr * sin ( radians(camAngle) ) +0 ;
      camera(camX, camY, camZ, 
      330.0, 331.0, 0.0, 
      0.0, 1.0, 0.0);
      lights();
      Ball ball;
      for (int i=0; i < balls.size(); i++) {
        ball = balls.get(i);
        ball.display();
      }
      if (time<speed)
        time++;
      // camZ--;
      camAngle+=.6;
      if (camAngle>360) 
        camAngle=0;
    
      fill(2, 2, 222);
      //sphere(222);
    } // func 
    //
    
    
    // =====================================================================
    
    void keyPressed() {
      time = 0;
    }
    
    // =====================================================================
    // Simple bouncing ball class
    
    class Ball {
      float startx;
      float starty;
      float startz;
    
      float targetx;
      float targety;
      float targetz;
    
      color myColor; 
    
      float w;
    
      float controlPointX1=random(-width, width);
      float controlPointY1=random(-width, width);
      float controlPointZ1=random(-width, width);
    
      float controlPointX2=random(-width, width);
      float controlPointY2=random(-width, width);
      float controlPointZ2=random(-width, width);
    
      Ball(float tempstartX, float tempstartY, float tempstartZ, 
      float tempX2, float tempY2, float tempZ2, 
      float tempW, color tempmyColor1 ) {
        startx = tempstartX;
        starty = tempstartY;
        startz = tempstartZ;
        w = tempW;
    
        targetx=tempX2;
        targety=tempY2;
        targetz=tempZ2;
    
        myColor=tempmyColor1;
      }
    
      void display() {
        fill(myColor); 
        noStroke();
        //float x1 = lerp(startx, targetx, time/speed);
        //float y1 = lerp(starty, targety, time/speed);
        // curve(startx, controlPointX1, controlPointX2, targetx);
        float x1 = curvePoint(controlPointX1, startx, targetx, controlPointX2, time/speed);
        float y1 = curvePoint(controlPointY1, starty, targety, controlPointY2, time/speed);
        float z1 = curvePoint(controlPointZ1, startz, targetz, controlPointZ2, time/speed); // lerp(startz, targetz, time/speed);
        //
        //ellipse(x1, y1, w, w);
        pushMatrix();
        translate(x1, y1, z1);
        sphere (w);
        popMatrix();
      }
    } // class   
    
    // =====================================================================
    
  • thanks for sharing, Sir Chris! will let you know if I'm having indigestion issues ... :P

  • when you in the 2D version more cleverly than I place the balls in the circle next to those in the rect it looks better (so one ball in the circle can fly to the position in the rect that is closest to him).

Sign In or Register to comment.