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) { 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?
]]>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:
changes to
Whereas, here's how to change it to use the old concept with the correct angles:
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)
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); }
]]>
Heh... vbovio's response is far more useful if you just want it to work : )
]]>This is what I made a while ago and it looks just like vbovio's
1 | void 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 | |
14 | void 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 | } |
]]>
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:
edit: oops had a problem, it's there now... It's all in blue
edit2: that looks bad, I uploaded one in white
]]>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:
should be:
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)
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.
You could just take the rounded rectangle rendering code from OpenLayer
]]>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.
]]>Search for the part which begins: if( roundness > 0.0 )
]]>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.
look for SuperEllipse equation; and SuperQuadrics in general, you can find interesting shapes using their equations, rounded rects can be achieved with superellipses.
]]>