Allegro.cc Forums » Programming Questions » Rounded Rects

Credits go to DanielH, orz, and vbovio for helping out!
 Rounded Rects
juvinious
Member #5,145
October 2004

I've been struggling with accomplishing this probably simple issue for some time now and can't figure it out. I don't understand the math in connecting 4 lines with arcs due to my limited knowledge in math.
Given this arc routine below, I'm trying to make a rounded rectangle using it.
(note that this isn't in allegro and I'd prefer it not to be since I'm not using it for this):

 1 void doArc(int centerX, int centerY, unsigned const int segments, float radius, float startAngle, float sweep) 2 { 3 float angle = startAngle * 3.14159265 / 180; // initial angle in radians 4 float angleInc = sweep * 3.14159265 /(180 * segments); // angle increment 5 float cx = centerX + radius * cos(angle), cy = centerY + radius * sin(angle); 6 float oldcx = cx, oldcy = cy; 7 for(unsigned int k = 1; k < segments; k++) 8 { 9 doLine(oldcx,oldcy,cx,cy,color); 10 oldcx = cx; 11 oldcy = cy; 12 cx += radius * cos(angle); 13 cy += radius * sin(angle); 14 angle += angleInc; 15 } 16 }

I'd like to be able to have my round rect routine to be something like:

```void doRoundRect(int x, int y, int width, int height, float radius)
{
// Needs arcs
}
```

Perhaps my arc routine is bad? Any suggestions?

__________________________________________
Paintown

 orz Member #565 August 2000 I haven't tried running your code or anything yet, but a few comments:1. I would guess that the stuff you want after the "// Needs arcs" comment should look roughly like: ```enum {NUM_ARC_SEGMENTS=4}; doArc(x+radius, y+radius, NUM_ARC_SEGMENTS, radius, 180, 90); doArc(x+radius, y+height-radius, NUM_ARC_SEGMENTS, radius, 90, 90); doArc(x+width-radius, y+radius, NUM_ARC_SEGMENTS, radius, 270, 90); doArc(x+width-radius, y+height-radius, NUM_ARC_SEGMENTS, radius, 0, 90); ``` 2. You're doArc is a bit weird I think. The problem is that you use angle as both the angle from the center to the edge of the circle and as the angle along the edge of the circle - these two angles are supposed to be perpendicular to each other, not the same. Here's an example of changing it to use only the angle from the center: ``` for(unsigned int k = 1; k < segments; k++) { doLine(oldangle,lor); oldcx = cx; oldcy = cy; cx += radius * cos(angle); cy += radius * sin(angle); angle += angleInc; } } ``` changes to ``` for(unsigned int k = 1; k < segments; k++) { angle += angleInc; cx = centerX + cos(angle); cy = centerY + sin(angle); doLine(oldcx,oldcy,cx,cy,color); oldcx = cx; oldcy = cy; } } ``` Whereas, here's how to change it to use the old concept with the correct angles: ``` float angle = startAngle * 3.14159265 / 180; // initial angle in radians float angleInc = sweep * 3.14159265 /(180 * segments); // angle increment float cx = centerX + radius * cos(angle), cy = centerY + radius * sin(angle); float oldcx = cx, oldcy = cy; ``` change so: ``` float angle = startAngle * 3.14159265 / 180; // initial angle in radians float angleInc = sweep * 3.14159265 /(180 * segments); // angle increment float cx = centerX + radius * cos(angle), cy = centerY + radius * sin(angle); float oldcx = cx, oldcy = cy; angle += 3.14159265/2; radius /= segments;//not sure about this line... ``` actually... I think screwed that one up... but that's the idea : )edit:oh yeah... why is k starting at 1 instead of 0?and the other problem was that you used the radius as the length of each segment, which it's not. (the length of each segment is equal to the radius times (angleInc converted to radians)
 vbovio Member #8,283 January 2007 ```void roundRect(BITMAP *bmp, int x1, int y1, int x2, int y2, int r, int c) { hline(bmp, x1+r, y1, x2-r, c); hline(bmp, x1+r, y2, x2-r, c); vline(bmp, x1, y1+r, y2-r, c); vline(bmp, x2, y1+r, y2-r, c); arc(bmp, x1+r, y1+r, itofix(64), itofix(128), r, c); arc(bmp, x2-r, y1+r, itofix(0), itofix(64), r, c); arc(bmp, x1+r, y2-r, itofix(128), itofix(192), r, c); arc(bmp, x2-r, y2-r, itofix(192), itofix(256), r, c); } ``` ---------------BovioSoft
 orz Member #565 August 2000 Heh... vbovio's response is far more useful if you just want it to work : )
DanielH
Member #934
January 2001

This is what I made a while ago and it looks just like vbovio's

 orz Member #565 August 2000 Are you trying to get a working function, or to understand the math of an arc-drawing function? For a working function, just use vbovio's code. For the math of an arc-drawing function:the first fix I proposed contained 2 errors that I've noticed:1a: I didn't change the starting value of k to 0 (I don't know why it was 1, but that isn't right). Otherwise, the function skips the last segment. 1b: I forgot to multiply the distance from the center by the radius. Thus: ``` for(unsigned int k = 1; k < segments; k++) { angle += angleInc; cx = centerX + cos(angle); cy = centerY + sin(angle); doLine(oldcx,oldcy,cx,cy,color); oldcx = cx; oldcy = cy; } ``` should be: ``` for(unsigned int k = 0; k < segments; k++) { angle += angleInc; cx = centerX + radius * cos(angle); cy = centerY + radius * sin(angle); doLine(oldcx,oldcy,cx,cy,color); oldcx = cx; oldcy = cy; } ``` The idea here is that we break up our arc of D degrees into N sub-arcs of D/N degrees, and we aproximate each subarc with a straight line connecting the endpoints. Each endpoint is calculated separately. The second, alternative fix I proposed contains... flaws...Just to be clear, the second fix, the one where the angle had PI/2 added to it to so that the angle was the angle along the edge rather than from the center, should NOT be combined with the first fix. They are alternatives to each other. Anyway, the flaws:2a. The names I used were misleading, chosen just to make the code changes as small as possible. Radius wasn't really conceptually divided by the number of segments, it's just that the scale of how far (cx,cy) was from the previous position for each segmented needed to be divided by the number of segments. New variables could be declared to hold the segment length to fix this... non-bug, but still an issue. 2b. even so, the segment length calculated was still wrong. Instead of radius/segments it should be something like... radius*angleInc, but, actually, even that only works if angleInc is small, otherwise the math is messier... maybe radius * sqrt(sqr(sin(angleInc)) + 1 + sqr(cos(angleInc)) - 2*cos(angleInc))... or maybe... equivalently?... radius * sin(angleInc) / sin((PI-angleInc)/2)... bleh, I'm getting confusedso... don't use method 2. it's too much work. 2c. the angle used was the angle at the point on the circle in question, ie the begining of each subarc, should have been the angle along the middle of the subarc... can fix by adding angleInc/2The idea of the second method was generally similar...we break up our arc of D degrees into N sub-arcs of D/N degrees, and we aproximate each subarc with a straight line connecting the endpoints. The difference is that endpoints are not calculated seperately... instead the first endpoint is calculated, and we figure that the arc is a line curving at a fixed rate, so we can calculate further endpoints progressively from the previous one. But this introduces an extra problem, because for this method we need to know the straight-line distance between each pair of endpoints, and that's a little messy to calculate... my best guess is radius * sin(angleInc) / sin((PI-angleInc)/2) (which, if angleInc is small, is aproximately radius * angleInc)