how to use bezier curve to approximate one quarter of a circle?

oatoat
edited December 2013 in How To...

may i ask how to use bezier curve to approximate the curve representing one quarter of a circle? Thanks!

Tagged:

Answers

  • oatoat
    edited December 2013

    the question is stimulated by the following video demonstrating the morphing of curve shape into rectangle shape: http://vis.berkeley.edu/papers/animated_transitions/

  • oatoat
    edited December 2013

    something like the following morphing process:

    Untitled

    any advices?

  • I presume you're talking about making a section of a circle with a bezier curve. You can use this formula to approximate the bezier curve to look like a section of a circle. L = 4 * tan(angle / 4) / 3, where L is the length of each control line.

    Also, here's some sample code proving the equation. It's lengthy, but you can input any angle:

    float angle = 94; //Change this value for angle of bezier
    float lnth;
    float radius = 200;
    PVector b1 = new PVector(0, -radius);
    PVector b2;
    
    
    void setup() {
      size(500, 500);
      lnth = 4 * tan(radians(angle/4))/3; //The equation responsible for the distance between control points.
      b2 = new PVector(-radius * lnth, -radius);
      strokeWeight(0);
      b1.rotate(radians(angle));
      b2.rotate(radians(angle));
    }
    
    void draw() {
      translate(width/2, height/2);
      noFill();
      stroke(0);
      bezier(0, -radius, radius * lnth, -radius, b2.x, b2.y, b1.x, b1.y);
      ellipseMode(CENTER);
      stroke(255, 0, 0);
      ellipse(0, 0, 406, 406);
    }
    

    -- MenteCode

  • You know the rules: 90 degrees for 1/4 of a circle.

  • oatoat
    edited December 2013

    Thanks, MenteCode and PhiLho!

    I have two questions:

    1. the solution seems to work only for angle between 0 and 180 degree (i mean looks still OK in terms of the approximation), is it?

    2. I edit MenteCode's code to use mouseX to control the angle (0-180), but the resulting bezier curve is constantly flicking. Not sure what's the source of the error. Hope you can help to point out what i'm missing here.

    捕获

    // MenteCode: making a section of a circle with a bezier curve
    // http://forum.processing.org/two/discussion/1797/how-to-use-bezier-curve-to-approximate-one-quarter-of-a-circle#Item_3  
    
    float angle = 90; //Change this value for angle of bezier
    float lnth;
    float radius = 120;
    
    PVector pStart = new PVector(0, -radius);
    PVector pCtrl1;
    PVector pCtrl2;
    PVector pEnd = new PVector(0, -radius);
    
    void setup() {
      size(500, 500);
    }
    
    void draw() {
      background(150);
    
      angle = map(mouseX, 0,width, 0,180);
    
      //The equation responsible for the distance between control points.
      lnth = 4 * tan(radians(angle/4))/3; 
    
      pCtrl1 = new PVector(radius * lnth, -radius);
      pCtrl2 = new PVector(-radius * lnth, -radius);
    
      pCtrl2.rotate(radians(angle));  
      pEnd.rotate(radians(angle));
    
      translate(width/2, height/2);
      noFill();
    
      stroke(0,255,0);
      strokeWeight(15);
      point(pStart.x, pStart.y);
      point(pCtrl1.x, pCtrl1.y);
      point(pCtrl2.x, pCtrl2.y);
      point(pEnd.x, pEnd.y);
    
      stroke(0);
      strokeWeight(10);  
      bezier(pStart.x, pStart.y,
             pCtrl1.x, pCtrl1.y, 
             pCtrl2.x, pCtrl2.y, 
             pEnd.x, pEnd.y);
    
      ellipseMode(CENTER);
      stroke(255, 0, 0);
      strokeWeight(3);
      ellipse(0, 0, radius*2, radius*2);
    
    }
    
  • updated code below solves the flicking issue by defining a class for the approximation of circle section by bezier curve:

    1

    2

    3

    // MenteCode: making a section of a circle with a bezier curve
    // http://forum.processing.org/two/discussion/1797/how-to-use-bezier-curve-to-approximate-one-quarter-of-a-circle#Item_3  
    
    float angle = 90; //Change this value for angle of bezier
    float radius = 100;
    
    CircleByBezier bcurve;
    
    void setup() {
      size(500, 500);  
      bcurve = new CircleByBezier();  
    }
    
    void draw() {
      background(150);
    
      angle = map(mouseX, 0,width, 0,360);
    
      translate(width/2, height/2);
      noFill();
    
      bcurve.display(angle, radius);
    
      // curve fit check reference
      ellipseMode(CENTER);
      stroke(255, 0, 0);
      strokeWeight(3);
      ellipse(0, 0, radius*2, radius*2);
    
    }
    
    class CircleByBezier{
      float angle, radius, lnth;
      PVector pStart, pCtrl1, pCtrl2, pEnd;
    
    //  BC(float _angle, float _radius){
    //    angle = _angle;
    //    radius = _radius;
    //    lnth = 4 * tan(radians(angle/4))/3; 
    //    
    //    pStart = new PVector(0, -radius);
    //    pCtrl1 = new PVector(radius * lnth, -radius);
    //    pCtrl2 = new PVector(-radius * lnth, -radius);
    //    pEnd   = new PVector(0, -radius);
    //    
    //    pCtrl2.rotate(radians(angle)); 
    //    pEnd.rotate(radians(angle)); 
    //  }
    
      void display(float _angle, float _radius){
        angle = _angle;
        radius = _radius;
        lnth = 4 * tan(radians(angle/4))/3; 
    
        pStart = new PVector(0, -radius);
        pCtrl1 = new PVector(radius * lnth, -radius);
        pCtrl2 = new PVector(-radius * lnth, -radius);
        pEnd   = new PVector(0, -radius);
    
        pCtrl2.rotate(radians(angle)); 
        pEnd.rotate(radians(angle));     
    
        stroke(0);
        strokeWeight(10);  
        bezier(pStart.x, pStart.y,
               pCtrl1.x, pCtrl1.y, 
               pCtrl2.x, pCtrl2.y, 
               pEnd.x,   pEnd.y);
    
        stroke(0,255,0);
        strokeWeight(15);
        point(pStart.x, pStart.y);
        point(pCtrl1.x, pCtrl1.y);
        point(pCtrl2.x, pCtrl2.y);
        point(pEnd.x, pEnd.y);   
      }
    }
    
  • ... further tidy up of the code:

    // MenteCode: making a section of a circle with a bezier curve
    // http://forum.processing.org/two/discussion/1797/how-to-use-bezier-curve-to-approximate-one-quarter-of-a-circle#Item_3  
    
    float angle = 90; //Change this value for angle of bezier
    float radius = 100;
    
    CircleByBezier bcurve;
    
    void setup() {
      size(1000, 1000);  
      bcurve = new CircleByBezier();  
    }
    
    void draw() {
      background(150);
    
      translate(width/2, height/2);
      noFill();
    
      angle = map(mouseX, 0,width, 0,360);
      bcurve.update(angle, radius);
      bcurve.display();
    
      // curve fit check reference
      ellipseMode(CENTER);
      stroke(255, 0, 0);
      strokeWeight(3);
      ellipse(0, 0, radius*2, radius*2);
    
    }
    
    class CircleByBezier{
      float angle, radius, lnth;
      PVector pStart, pCtrl1, pCtrl2, pEnd;
    
      void update (float _angle, float _radius) {
        angle = _angle;
        radius = _radius;
        lnth = 4 * tan(radians(angle/4))/3; 
    
        pStart = new PVector(0, -radius);
        pCtrl1 = new PVector(radius * lnth, -radius);
        pCtrl2 = new PVector(-radius * lnth, -radius);
        pEnd   = new PVector(0, -radius);
    
        pCtrl2.rotate(radians(angle)); 
        pEnd.rotate(radians(angle)); 
      }
    
      void display () {
        stroke(0);
        strokeWeight(10);  
        bezier(pStart.x, pStart.y,
               pCtrl1.x, pCtrl1.y, 
               pCtrl2.x, pCtrl2.y, 
               pEnd.x,   pEnd.y);
    
        stroke(0,255,0);
        strokeWeight(15);
        point(pStart.x, pStart.y);
        text("start pt", pStart.x+8, pStart.y);
        point(pCtrl1.x, pCtrl1.y);
        text("ctrl pt1", pCtrl1.x+8, pCtrl1.y);
        point(pCtrl2.x, pCtrl2.y);
        text("ctrl pt2", pCtrl2.x+8, pCtrl2.y);
        point(pEnd.x, pEnd.y);  
        text("end pt", pEnd.x+8, pEnd.y); 
      }
    }
    
  • sorry, but the transition from an arc to straight line is still not clear to me ... any suggestions? thanks!

  • something like the following transition:

    Untitled

  • any suggestions?

  • the original transformation was easier, just a case of manipulating the control points, making them gradually closer to the horizontal. um, a diagram would help here, but i'm at work...

    step 4 in the above diagram puzzles me. do the lines have width now? if not, how do you expect it to bulge like that? in reality you wouldn't have 4 points though, you'd have 10s, equally spread out along the curve with quads between them.

    from rectangle to circle is easy enough - move the points further and further away from the centre, clamp them at the radius. um the bulging between p2 and p3 and between p1 and p4 suggests many points between those two pairs as well though. in any case, you'll need more than the 4 points you show.

  • oatoat
    edited December 2013

    Hi, Koogs, thanks! yes, the control points are not shown in the diagram.

    I'm thinking about using just arc to achieve this morphing effect (see the code below):

    the length of the arc/line is kept as the same

    Screen Shot 2013-12-08 at 3.59.58 pm

    the mouseX is used to control the radius of the circle from which the arc is took, i.e the degree of the arc:

    Screen Shot 2013-12-08 at 4.00.24 pm

    Screen Shot 2013-12-08 at 4.00.33 pm

    once the radius reaches the infinite (i.e. the angle subtended by the arc is zero), just simply draw a line of the same length:

    Screen Shot 2013-12-08 at 4.00.44 pm

    float r;
    float len = 200;
    float theta; 
    PVector cen;
    
    void setup()
    {
      size(500, 500);
      smooth();
      cen = new PVector(width/2, height/2);
      println(cen.x);
    }
    
    void draw()
    {
      background(222);
      strokeWeight(10);
      strokeCap(SQUARE); 
    
      theta = map(mouseX, 10,width-10, 0,360);
      fill(0);
      text("theta: "+theta+" degree", 10,20);
    
      if (theta <= 0.0) { 
        line(cen.x, cen.y-50, cen.x+len, cen.y-50);
      } else if ((theta > 0) && (theta <= 360)) {  
        noFill();
        r = len/radians(theta);
        text("r: "+r, 10,40);
        arc(cen.x, cen.y-50+r, 
            2*r, 2*r, 
            -PI/2, -PI/2+radians(theta));
      }
    }
    
  • ... update

    Screen Shot 2013-12-08 at 5.09.11 pm

    TheArc arc1, arc2;
    float h = 20;
    float len = 200;
    
    void setup()
    {
      size(500, 500);
      smooth();
    
      arc1 = new TheArc(new PVector(width/2, height/2-50), len, 90);
      arc2 = new TheArc(new PVector(width/2, height/2-50+h), len-h*PI/2, 90);
    }
    
    void draw()
    {
      background(222);
      strokeWeight(10);
      strokeCap(SQUARE); 
    
      float amt = constrain(mouseX, 10, width-10);
    
      arc1.theta = map(amt, 10,width-10, 0,360);
      arc1.update();
      arc1.display();
    
      arc2.theta = map(amt, 10,width-10, 0,360);
      if (arc2.theta == 0.0) {
        arc2.len = arc1.len;
      } else {   
        arc2.len = (arc1.r-h)*arc1.len/arc1.r;
      }
      arc2.update();
      arc2.display();
    
      fill(0);
      text("theta: "+map(amt, 10,width-10, 0,360)+" degree", 10,20);
    
    }
    
    class TheArc {
      PVector p1;
      float len;
      float r;
      float theta;
    
      TheArc (PVector _p1, float _len, float _theta) {
        p1 = new PVector(_p1.x, _p1.y);
        len = _len;
        theta = _theta;
      }
    
      void update(){
        r = len/radians(theta);    
      }
    
      void display(){
        if (theta <= 0.0) { 
          line(p1.x, p1.y, p1.x+len, p1.y);
        } else if ((theta > 0) && (theta <= 360)) {  
          noFill();
          arc(p1.x, p1.y+r, 
              2*r, 2*r, 
              -PI/2, -PI/2+radians(theta));
        }    
      }
    }
    
Sign In or Register to comment.