Draw circle shape with vertex

I try to draw a circle with vertex, but my result is very dirty.
I read a few article about that but I don’t find a solution ready to eat, so I coded something like I think possible to work but…so if anybody have suggestion !

http://spencermortensen.com/articles/bezier-circle/

/*
* DRAW CIRCLE 
*/
PVector [] pts = new PVector[4];
PVector [] pts_a = new PVector[4];
PVector [] pts_b = new PVector[4];
void setup() {
  size(400,400,P2D);
  float radius = 100;
  PVector offset = new PVector(width/2,height/2);
 
  // normal position
  pts[0] = new PVector(0,-1);
  pts[1] = new PVector(1,0);
  pts[2] = new PVector(0,1);
  pts[3] = new PVector(-1,0);  

  // kappa
  int n = pts.length;
  float magic_number = (4./3.)*tan(PI/(2*n));

  pts_a[0] = pts[0].copy().add(-magic_number,0);
  pts_a[1] = pts[1].copy().add(0,-magic_number);
  pts_a[2] = pts[2].copy().add(magic_number,0);
  pts_a[3] = pts[3].copy().add(0,magic_number);

  pts_b[0] = pts[0].copy().add(magic_number,0);
  pts_b[1] = pts[1].copy().add(0,magic_number);
  pts_b[2] = pts[2].copy().add(-magic_number,0);
  pts_b[3] = pts[3].copy().add(0,-magic_number);


  // finalize position
  for(int i = 0 ; i < pts.length ; i++) {
    pts[i].mult(radius).add(offset);
    pts_a[i].mult(radius).add(offset);
    pts_b[i].mult(radius).add(offset);
  }
}

void draw() {
  background(125);
  fill(255);
  stroke(0);
  strokeWeight(1);

  beginShape();
  vertex(pts[0].x,pts[0].y);
  bezierVertex(pts_a[0].x,pts_a[0].y,pts_b[0].x,pts_b[0].y,pts[0].x,pts[0].y);
  bezierVertex(pts_a[1].x,pts_a[1].y,pts_b[1].x,pts_b[1].y,pts[1].x,pts[1].y);
  bezierVertex(pts_a[2].x,pts_a[2].y,pts_b[2].x,pts_b[2].y,pts[2].x,pts[2].y);
  bezierVertex(pts_a[3].x,pts_a[3].y,pts_b[3].x,pts_b[3].y,pts[3].x,pts[3].y);
  endShape();

  strokeWeight(10);
  for(int i = 0 ; i < pts.length ; i++) {
    stroke(255,0,0);
    point(pts[i].x,pts[i].y);
    stroke(0,255,0);
    point(pts_a[i].x,pts_a[i].y);
    stroke(0,255,0);
    point(pts_b[i].x,pts_b[i].y);
  }
}

1 Like

Hi @Stanlepunk,

By eyeballing the green dots, it looks like the control points of the circle are ok, so maybe the order in which points are supplied to bezierVertex needs revising?

For debugging purposes, I find it helpful to distinguish the first from the second control point. This way I know which direction the circle is winding. For that reason, I began by changing one set of green dots to blue:

stroke(0, 0, 255);
point(pts_b[i].x, pts_b[i].y);

Then I updated the bezierVertex calls:

  beginShape();
  vertex(pts[0].x, pts[0].y);
  bezierVertex(pts_b[0].x, pts_b[0].y, 
    pts_a[1].x, pts_a[1].y, 
    pts[1].x, pts[1].y);
  bezierVertex(pts_b[1].x, pts_b[1].y, 
    pts_a[2].x, pts_a[2].y, 
    pts[2].x, pts[2].y);
  bezierVertex(pts_b[2].x, pts_b[2].y, 
    pts_a[3].x, pts_a[3].y, 
    pts[3].x, pts[3].y);
  bezierVertex(pts_b[3].x, pts_b[3].y, 
    pts_a[0].x, pts_a[0].y, 
    pts[0].x, pts[0].y);
  endShape();

The first control point supplied to the bezierVertex function call is associated with the second anchor point of the previous bezierVertex function call (or, at the curve’s beginning, with the anchor point given to the vertex call).

If you know about creating classes, a little organization goes a long way. One way to go about it, below, is an object-oriented approach (simplified from a p5.js version here ). First, a knot class which stores two control points along with one anchor point:

static class Knot {
  PVector coord = new PVector();
  PVector foreHandle = new PVector();
  PVector rearHandle = new PVector(); 

  Knot(float coordX, float coordY, float coordZ, 
    float foreX, float foreY, float foreZ, 
    float rearX, float rearY, float rearZ) {
    set(coordX, coordY, coordZ, 
      foreX, foreY, foreZ, 
      rearX, rearY, rearZ);
  }

  Knot set(float coordX, float coordY, float coordZ, 
    float foreX, float foreY, float foreZ, 
    float rearX, float rearY, float rearZ) {
    coord.set(coordX, coordY, coordZ);
    foreHandle.set(foreX, foreY, foreZ);
    rearHandle.set(rearX, rearY, rearZ);
    return this;
  }

  static Knot fromAngle(float angle, float radius, float handleMag) {
    float cosAngle = cos(angle);
    float sinAngle = sin(angle);

    float cox = cosAngle * radius;
    float coy = sinAngle * radius;

    // Tangent is perpendicular to coord. perp(x, y) := (-y, x).
    float fhx = cox - sinAngle * handleMag;
    float fhy = coy + cosAngle * handleMag;

    float rhx = cox + sinAngle * handleMag;
    float rhy = coy - cosAngle * handleMag;

    return new Knot(
      cox, coy, 0.0, 
      fhx, fhy, 0.0, 
      rhx, rhy, 0.0);
  }
}

It also defines the creation of points from polar coordinates. Then a curve object stores a list of the above.

static class Curve {
  boolean loop = false;
  Knot[] knots;

  Curve(boolean loop, Knot... knots) {
    this.loop = loop;
    this.knots = knots;
  }

  void draw(PGraphics renderer) {
    int knotLength = knots.length;
    if (knotLength <= 2) {
      // Ignores case where curve with 2 knots could
      // be drawn by a single bezierCurve function.
      return;
    }

    Knot prevKnot = knots[0];
    Knot currKnot;
    PVector coord, foreHandle, rearHandle;
    int end = loop ? knotLength + 1: knotLength;

    renderer.beginShape();
    coord = prevKnot.coord;
    renderer.vertex(coord.x, coord.y);

    for (int i = 1; i < end; ++i) {
      currKnot = knots[i % knotLength];
      foreHandle = prevKnot.foreHandle;
      coord = currKnot.coord;
      rearHandle = currKnot.rearHandle;

      renderer.bezierVertex(
        foreHandle.x, foreHandle.y, 
        rearHandle.x, rearHandle.y, 
        coord.x, coord.y);

      prevKnot = currKnot;
    }
    renderer.endShape();
  }

  static Curve circle(int count, float offsetAngle, float radius) {
    Knot[] knots = new Knot[count];
    float invKnCt = 1.0 / (float)count;
    float toAngle = TWO_PI * invKnCt;
    float mag = radius * (4.0 / 3.0) * tan(HALF_PI * invKnCt);
    for (int i = 0; i < count; ++i) {
      float angle = offsetAngle + i * toAngle;
      knots[i] = Knot.fromAngle(angle, radius, mag);
    }
    return new Curve(true, knots);
  }
}

This can be tested out in a sketch with

Curve circ;

void setup() {
  size(512, 512);
  circ = Curve.circle(4, 0.0, 128.0);
}

void draw() {
  background(255.0);
  translate(width * 0.5, height * 0.5);
  circ.draw(g);
}
4 Likes

Whaouuuu, very interesting approach of class, I’m familiar with it, but your way is very cool, I love this part circ = Curve.circle(4, 0.0, 128.0);So I take a time to study deeply the possibility to customize that to my way, because my final purpose is create a strange circle with a misssing part. I think with our system on on the highway to hell :slight_smile: Thanks a lot for your magic code.

Related past discussions about squircles / squarcles:

Thx for the gold mine… I miss the 3 on 4 on my research of solution :slight_smile: