I’m trying to make some buttons based on Tesla’s UI which has round and rounded rectangle buttons and found a weird issue. If you run rect(x, y, n, n, n/2); (in CORNER or CENTER mode) I would think you should get a circle because the corner radius is half of the w and h but you don’t. It’s more like a squircle. In CAD or vector software it would be a circle. What gives?
If you need an circle you can just use ellipse(x,y,width,height);
.
I’m trying to get a rectangle with rounded ends but if you if you use h/2 for the radius you get more of smushed ends that don’t look right. The issue is most evident though by trying to make a circle with rect.
Hi EliTheiceMan,
Good question!
If you look at the way they draw rounded rectangle, they use quadraticVertex()
:
So they are using bezier curves to draw the rounded edges. And the way they are using the bezier curves is by putting the control points on the corner of the rectangle like so:
The thing is that you can’t draw a perfect circle with bezier curves so you’ll never get perfect circles with this method.
Although there might be a way to make it appears a bit more like a circle by putting the control point in a very specific place.
Thank you, that is very interesting! Looks like I need to do more research on Bezier math sometime but I will just deal with my Bezier buttons for now! It’s only a minor detail but was really bugging me!
Now you can also create your own rectangle function.
Here an example. I did it quikly to demonstrate that you can have the behavior that you expect but you would need to put a bit more work into it to prevent users to put too high value in the rounding parameters and to create several version to match the behavior of the standard rect()
.
float f = 0.552284749831;
void setup() {
size(500, 500);
background(20);
noFill();
stroke(200);
strokeWeight(1);
myRect(100, 100, 300, 300, 150, 150, 150, 150);
}
void myRect(float x1, float y1, float lx, float ly, float tl, float tr, float br, float bl) {
float x2 = x1 + lx;
float y2 = y1 + ly;
beginShape();
if (tr != 0) {
vertex(x2 - tr, y1);
bezierVertex(x2 - tr + f * tr, y1, x2, y1 + tr - f * tr, x2, y1 + tr);
} else {
vertex(x2, y1);
}
if (br != 0) {
vertex(x2, y2-br);
bezierVertex(x2, y2 - br + f * br, x2 - br + f * br, y2, x2 - br, y2);
} else {
vertex(x2, y2);
}
if (bl != 0) {
vertex(x1+bl, y2);
bezierVertex(x1 + bl - f * bl, y2, x1, y2 - bl + f * bl, x1, y2 - bl);
} else {
vertex(x1, y2);
}
if (tl != 0) {
vertex(x1, y1+tl);
bezierVertex(x1, y1 + tl - f * tl, x1 + tl - f * tl, y1, x1 + tl, y1);
} else {
vertex(x1, y1);
}
endShape(CLOSE);
}
For the record I opened a new issue on github:
+1 for “squircle”. Very evocative.
@jb4x walks through a great approach to rolling your own rounding parameters – if you are interested in different approaches to morphing between a circle and a square then you should also check out the “Morph” example from the Processing example set.
https://processing.org/examples/morph.html
That example gives a point-based shape that you can animate between a square state and a circle state.
a test with LINEs and ARCs show a issue about the fill, thats tricky.
void my_rounded_rect_using_arcs(float x0,float y0,float wx,float wy,float r,float strokeW) {
ellipseMode(CENTER);
strokeWeight(strokeW);
line(x0+r,y0,x0+wx-r,y0); arc(x0+wx-r,y0+r,2*r,2*r,TWO_PI-HALF_PI,TWO_PI);
line(x0+wx,y0+r,x0+wx,y0+wy-r); arc(x0+wx-r,y0+wy-r,2*r,2*r,0,HALF_PI);
line(x0+r,y0+wy,x0+wx-r,y0+wy); arc(x0+r,y0+wy-r,2*r,2*r,PI-HALF_PI,PI);
line(x0,y0+r,x0,y0+wy-r); arc(x0+r,y0+r,2*r,2*r,PI,PI+HALF_PI);
//fill the area between the lines
noStroke();
rect(x0+r,y0+strokeW/2,wx-2*r,wy-strokeW+1); rect(x0+strokeW/2,y0+r,wx-strokeW+1,wy-2*r);
}
and a real SQUIRCLE
// Squircle is a special case of a superellipse
// https://en.wikipedia.org/wiki/Squircle
// ( x − a )**4 + ( y − b )**4 = r**4
// x ( t ) = | cos t | 1/2 * r * sgn( cos t )
// y ( t ) = | sin t | 1/2 * r * sgn( sin t )
// code by Dan Anderson
void setup() {
size(300, 300);
background(200, 200, 0);
stroke(0, 200, 200);
strokeWeight(3);
fill(0, 100, 0);
}
void my_squircle(float r,float wx,float wy) {
float x1, y1;
beginShape();
for (float t = 0; t < TWO_PI; t += TWO_PI/160) {
x1 = pow(abs(cos(t)), 0.5) * r*wx * sign(cos(t));
y1 = pow(abs(sin(t)), 0.5) * r*wy * sign(sin(t));
vertex(x1, y1);
}
endShape(CLOSE);
}
float sign(float input) {
if (input < 0) { return -1.0; }
if (input > 0) { return 1.0; }
return 0.0;
}
void draw() {
background(200, 200, 0);
translate(width/2, height/2);
my_squircle(mouseX/2.0,1.0,float(mouseY)/float(height));
}