Polygon with rounded corners

5.7k views Asked by At

I'm using Processing to try to draw something similar to the image below:

enter image description here

I've seen some examples using bezierVertex for regular shapes, like this one, but I would like to build a simple drawing method where I can specify my shape's vertex and then the corner radius. Something like myRoundedShape(x1, y1, x2, y2, x3, y3... xn, yn, cornerRadius) Any ideas?

2

There are 2 answers

7
Mike 'Pomax' Kamermans On BEST ANSWER

If you need rounded corners for arbitrary angles, Bezier curves are pretty much the worst possible choice, because the maths is stupidly complex. instead, let's use Catmull-Rom curves, because they're defined as "the curve goes through the following points: ..." instead of being simply controlled by points.

For any 2 edges between 3 points, p1, p2 and p3, we can create new points p2l and p2r on the left and right of p2, along the edges p1--p2 and p2--p3, at a fixed distances "radius" from p2:

dx = p2.x-p1.x
dy = p2.y-p1.y
p2l = {x: p2.x - radius * dx, y: p2.y - radius * dy}
p2l_guide = {x: p2.x - 3 * radius * dx, y: p2.y - 3 * radius * dy}

and

dx = p3.x-p2.x
dy = p3.y-p2.y
p2r = {x: p2.x + radius * dx, y: p2.y + radius * dy}
p2r_guide = {x: p2.x + 3 * radius * dx, y: p2.y + 3 * radius * dy}

now our edges will look like p1--p2l and p2r--p3, and the trick is to "connect" p2l and p2r in a nice, arc-ish way. So let's use the curveVertex:

beginShape();
curveVertex(p2l_guide.x,p2l_guide.y);
curveVertex(p2l.x,p2l.y);
curveVertex(p2r.x,p2r.y)
curveVertex(p2r_guide.x,p2r_guide.y);
endShape();

Of course, you're dealing with lots of edge pairs, so you'll need to preprocess your list of coordinates to become lists of tubles {p2l_guide,p2l,p2r,p2r_guide} instead, and then connect those as straight line p2r(n)--p2l(n+1) and then add the connection curve from p2l to p2r guided by the ..._guide points. In code: http://jsfiddle.net/drzp6L0g/3

This solution does leave us with the mystery variable f that controls the guide strength, so to solve that we would need bezier curves, and we'd need to determine the same p2l and p2r points, and then apply some trigonometry to figure out what our control points would need to be in order to effect an approximation a of circular arc. It'd be more accurate, but also more work.

The ideal solution, of course, would be to simply use an arcTo command, but Processing doesn't actually have one. It has an arc() command, detached from creating shapes (so if you just need outlines... that might do just fine for you!) but if you need a filled shape, no luck.

2
Mike 'Pomax' Kamermans On

Revisiting this, when we want rounded corners with "a radius" we don't actually want circular connections, we just want the isosceles triangle "tip" to be rounded off. That's actually really easy with Bezier curves, so here goes.

We still need our offset points p2l and p2r, because we're going to round off the triangle p2l--p2--p2r:

For any 2 edges between 3 points, p1, p2 and p3, we can create new points p2l and p2r on the left and right of p2, along the edges p1--p2 and p2--p3, at a fixed distances "radius" from p2:

dx = p2.x-p1.x
dy = p2.y-p1.y
p2l = {x: p2.x - radius * dx, y: p2.y - radius * dy}

and

dx = p3.x-p2.x
dy = p3.y-p2.y
p2r = {x: p2.x + radius * dx, y: p2.y + radius * dy}

(Note that we don't need any additional guide points). We can now define the rounding operation as:

start = p2l
c1 = point somewhere on line p2l--p2, ratio distance `t` from p2l and `1-t` from p2
c2 = point somewhere on line p2r--p2, using same ratio distance
end = p2r

If we pick a ratio distance of 0, then c1 == p2l and c2 == p2r, and we get a straight line. If we pick ratio distance 1, then c1 == c2 == p2, and we have the most pointy curve possible. For decent looking curves, a value of 0.5 or 0.75 will do the trick just fine. So first, let's define that c1/c2 abstracting function:

float[] roundIsosceles(Point p1, Point p2, Point p3, float t) {
  float mt = 1-t,
        c1x = (mt*p1.x + t*p2.x),
        c1y = (mt*p1.y + t*p2.y),
        c2x = (mt*p3.x + t*p2.x),  
        c2y = (mt*p3.y + t*p2.y);
  return new float[]{ c1x, c1y, c2x, c2y };
} 

And now we can create out proper shape, based on the closed list of points:

beginShape();
Point p1,p2,p3;
for(int i=0, last=closed.size(); i<last; i+=3) {
  p1 = closed.get(i);
  p2 = closed.get(i+1);
  p3 = closed.get(i+2);

  // rounded isosceles triangle connector values:
  float[] c = roundIsosceles(p1, p2, p3, 0.75);

  // tell Processing that we have points to add to our shape:
  vertex(p1.x,p1.y);
  bezierVertex(c[0], c[1], c[2], c[3], p3.x, p3.y); 
}
endShape(CLOSE);

And done. What's the result? As running code, this: http://jsfiddle.net/drzp6L0g/13, and as picture, this:

enter image description here

And a simple demonstrator of the difference between ratios 0, 0.25, 0.5, 0.75 and 1:

enter image description here