Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Rounded Rects

Credits go to DanielH, orz, and vbovio for helping out!
This thread is locked; no one can reply to it. rss feed Print
Rounded Rects
juvinious
Member #5,145
October 2004
avatar

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):

1void 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)
{
        doLine(x+radius, y, x+width-radius, y);
  doLine(x+radius, y+height, x+width-radius,y+height);
  doLine(x, y+radius,x, y+height-radius);
  doLine(x+width, y+radius,x+width, y+height-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
avatar

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

1void drawRoundedRect( BITMAP *bitmap, int x1, int y1, int x2, int y2, int radius, int color )
2{
3 arc( bitmap, x1 + radius, y1 + radius, itofix( 64 ), itofix( 128 ), radius, color );
4 arc( bitmap, x2 - radius, y1 + radius, itofix( 0 ), itofix( 64 ), radius, color );
5 arc( bitmap, x2 - radius, y2 - radius, itofix( 192 ), itofix( 0 ), radius, color );
6 arc( bitmap, x1 + radius, y2 - radius, itofix( 128 ), itofix( 192 ), radius, color );
7 
8 line( bitmap, x1 + radius, y1, x2 - radius, y1, color );
9 line( bitmap, x1 + radius, y2, x2 - radius, y2, color );
10 line( bitmap, x1, y1 + radius, x1, y2 - radius, color );
11 line( bitmap, x2, y1 + radius, x2, y2 - radius, color );
12}
13 
14void drawRoundedFilledRect( BITMAP *bitmap, int x1, int y1, int x2, int y2, int radius, int color )
15{
16 rectfill( bitmap, x1 + radius,
17 y1,
18 x2 - radius,
19 y2,
20 color );
21 
22 rectfill( bitmap, x1,
23 y1 + radius,
24 x1 + radius,
25 y2 - radius,
26 color );
27 
28 rectfill( bitmap, x2 - radius,
29 y1 + radius,
30 x2,
31 y2 - radius,
32 color );
33 
34 circlefill( bitmap, x1 + radius, y1 + radius, radius, color );
35 circlefill( bitmap, x2 - radius, y1 + radius, radius, color );
36 circlefill( bitmap, x1 + radius, y2 - radius, radius, color );
37 circlefill( bitmap, x2 - radius, y2 - radius, radius, color );
38}

juvinious
Member #5,145
October 2004
avatar

vbovio: I'm not using allegro, although usefull in terms of getting it done it doesn't help me in the understanding of my problem. Specifically in regards to creating an arc.

orz: Thanks for the input, the suggestions you provided gave me better results with my dealing with the problem, though I'm still not seeing it.
About the arc routine I snagged it off the net after a search with google, so I can assume that it's not perfect or even correct. Utilizing your changes this is what it looks like, better than what I was trying to do in any event:

591177

edit: oops had a problem, it's there now... It's all in blue

edit2: that looks bad, I uploaded one in white

__________________________________________
Paintown

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 confused
so... 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/2

The 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)

juvinious
Member #5,145
October 2004
avatar

orz: Ah ok I misread what you were saying, but now I get what you were conveying. Thanks for the help, it works as expected now.
And thanks to the others for their input. :)

__________________________________________
Paintown

Fladimir da Gorf
Member #1,565
October 2001
avatar

You could just take the rounded rectangle rendering code from OpenLayer :)

OpenLayer has reached a random SVN version number ;) | Online manual | Installation video!| MSVC projects now possible with cmake | Now alvailable as a Dev-C++ Devpack! (Thanks to Kotori)

juvinious
Member #5,145
October 2004
avatar

Heh, well I looked at Rectangle.cpp if that's what you are talking about. Either I didn't see it or couldn't figure it out. ::)

__________________________________________
Paintown

Fladimir da Gorf
Member #1,565
October 2001
avatar

Search for the part which begins: if( roundness > 0.0 )

OpenLayer has reached a random SVN version number ;) | Online manual | Installation video!| MSVC projects now possible with cmake | Now alvailable as a Dev-C++ Devpack! (Thanks to Kotori)

Mr. Big
Member #6,196
September 2005

You can modify the equation of the circle to create rounded rectangles.
r^n = x^n + y^n (n > 1)
The higher 'n', the less rounded your rectangle would look.

vbovio
Member #8,283
January 2007

look for SuperEllipse equation; and SuperQuadrics in general, you can find interesting shapes using their equations, rounded rects can be achieved with superellipses.

---------------
BovioSoft

Go to: