Related
I'm trying to draw a diagonal semicircle. So far I've only been able to draw ones that begin and end on a horizontal or vertical axis, like this:
I've tried modifying the code to tilt the circle, but it doesn't work. Can someone please tell me where I've gone wrong, this is infuriating!
float theta, tanTheta, x, y, dx, dy;
int circle_points = 1000, radius = 70;
glBegin(GL_POLYGON);
for(int i = 0; i < circle_points; i++)
{
dx = pts[1].x - pts[0].x;
dy = pts[1].y - pts[0].y;
tanTheta = tan(dy / dx);
// get the inverse
theta = atan(tanTheta);
x = radius * cos(theta);
y = radius * sin(theta);
glVertex2f(x, y);
}
glEnd();
I recommend to calculate the angle to the start point and the angle to the end point by atan2.
Interpolate the angle between the start angle and the end angle and draw a line along the corresponding points on the circe:
float ang_start, ang_end, theta, x, y;
ang_start = atan2( pts[0].y, pts[0].x );
ang_end = atan2( pts[1].y, pts[1].x );
if ( ang_start > ang_end )
ang_start -= 2.0f * M_PI;
glBegin(GL_LINE_STRIP);
for(int i = 0; i <= circle_points; i++)
{
float w = (float)i / (float)circle_points;
float theta = ang_start + w * ( ang_end - ang_start );
x = radius * cos(theta);
y = radius * sin(theta);
glVertex2f(x, y);
}
glEnd();
In my program I am currently rendering circles using the Midpoint Circle Algorithm with the following code.
void drawcircle(int x0, int y0, int radius)
{
int x = radius-1;
int y = 0;
int dx = 1;
int dy = 1;
int err = dx - (radius << 1);
while (x >= y)
{
putpixel(x0 + x, y0 + y);
putpixel(x0 + y, y0 + x);
putpixel(x0 - y, y0 + x);
putpixel(x0 - x, y0 + y);
putpixel(x0 - x, y0 - y);
putpixel(x0 - y, y0 - x);
putpixel(x0 + y, y0 - x);
putpixel(x0 + x, y0 - y);
if (err <= 0)
{
y++;
err += dy;
dy += 2;
}
if (err > 0)
{
x--;
dx += 2;
err += (-radius << 1) + dx;
}
}
}
But my question is, is it possible to have this function work the same way, but split the circle into 4 separate sections? i.e. so rather than rendering a normal circle, it would look somewhat like this
Putting holes in circles
The implementation you had had some overlapping pixels where the 8 subsections meet. Also the circle was the wrong diameter (should always be even because radius * 2) the cause of some of the overlap.
To fix the size and remove the overlap I move half the circle out (to the right down) by one pixel and prevented half the last iteration pixels being rendered.
Also the statement if(err <= 0) is not needed as at that point err will always meet that condition. The y offset steps up 1 pixel each iteration, the err is to find when x needs to step.
Easy solution
With that the solution to your question regarding putting holes in the circle. You just need a counter to count down the number of pixels you want to skip. While that counter is > 0 don't draw any pixels.
I added it to the snippet below as a 4th argument hole. An int that is half the size of the hole at the top, bottom, left and right of the circle.
So if you want a 20 pixel hole then the last argument is 10.
See right circles in image below.
Limit and geek stuff
Note that the max size of hole is sin(PI / 4) * radius if you make it bigger, no pixels will be drawn. As a side note the number of pixel drawn (without the holes) is approx sin(PI / 4) * radius * 8 - 4 which is almost 10% less than the circumference.
Solution for answer
void drawcircle(int x0, int y0, int radius, int hole) {
int x = radius-1;
int y = 0;
int dx = 1;
int dy = 1;
int err = dx - (radius << 1);
int x1 = x0 + 1;
int y1 = x0 + 1;
while (x >= y) {
if(hole == 0){
putpixel(x1 + x, y1 + y);
putpixel(x0 - x, y0 - y);
putpixel(x0 - y, y1 + x);
putpixel(x1 + x, y0 - y);
if (x > y) {
putpixel(x1 + y, y1 + x);
putpixel(x0 - x, y1 + y);
putpixel(x0 - y, y0 - x);
putpixel(x1 + y, y0 - x);
}
} else {
hole--;
}
y++;
err += dy;
dy += 2;
if (err > 0) {
x--;
dx += 2;
err += (-radius << 1) + dx;
}
}
}
Also as I geeked out on this function as it makes some good high performance shapes like rounded cross and boxes with only very minor changes that I thought worth sharing. See image
To get other shapes...
Add back the y err test and then change the delta X, and delta y error change to values other than 2.
if(err <= 0){ // needed
y++;
err += dy;
dy += 8; << change this
}
if (err > 0) {
x--;
dx += 8; << change this
err += (-radius << 1) + dx;
}
// See image in answer
// 8 made bottom right cross,
// 18 top right
// 28 top left
// 0.8 bottom left
The image shows the shapes and the result of the holes
I'm using SDL2.
The only way I can find to draw a shape is with the line, rect and pixel functions, as explained here.
Apart from using trig or the "equation of a circle", how could I draw a curve? How about general vector graphics?
Is SDL an appropriate starting point or should I look elsewhere?
This is an example of the Midpoint Circle Algorithm as referenced above. It doesn't require a math library and is very fast. (Renders in about 500 microseconds) This is what Windows uses/used to rasterize circles.
void DrawCircle(SDL_Renderer * renderer, int32_t centreX, int32_t centreY, int32_t radius)
{
const int32_t diameter = (radius * 2);
int32_t x = (radius - 1);
int32_t y = 0;
int32_t tx = 1;
int32_t ty = 1;
int32_t error = (tx - diameter);
while (x >= y)
{
// Each of the following renders an octant of the circle
SDL_RenderDrawPoint(renderer, centreX + x, centreY - y);
SDL_RenderDrawPoint(renderer, centreX + x, centreY + y);
SDL_RenderDrawPoint(renderer, centreX - x, centreY - y);
SDL_RenderDrawPoint(renderer, centreX - x, centreY + y);
SDL_RenderDrawPoint(renderer, centreX + y, centreY - x);
SDL_RenderDrawPoint(renderer, centreX + y, centreY + x);
SDL_RenderDrawPoint(renderer, centreX - y, centreY - x);
SDL_RenderDrawPoint(renderer, centreX - y, centreY + x);
if (error <= 0)
{
++y;
error += ty;
ty += 2;
}
if (error > 0)
{
--x;
tx += 2;
error += (tx - diameter);
}
}
}
If you want to write your own circle drawing function, then I'd suggest adapting the midpoint algorithm to SDL2 by drawing pixels.
Curves would be done similarly, but would use more of an ellipses drawing algorithm.
Actual vector graphics start to get much more complicated, and you'd probably have to find something that renders SVG files, which I'm not sure there are many options for SDL2.
However, if you would rather simply have functions that you can work with I'd suggest going straight to SDL2_gfx instead. It has many more functions already implemented for you to work with.
SDL allows for third party libs to draw on a texture. If cairo was desirable, it could be used in a function like this:
cairo_t*cb(cairo_t*cr)
{cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_rectangle(cr, 10, 20, 128, 128);
cairo_stroke(cr);
return cr;
}
then cb can be passed to this function:
cairo_t*cai(SDL_Window*w,SDL_Renderer*r,cairo_t*(*f)(cairo_t*))
{int width, height, pitch;void *pixels;
SDL_GetWindowSize(w, &width, &height);
SDL_Texture*t=SDL_CreateTexture(r,SDL_PIXELFORMAT_ARGB8888,SDL_TEXTUREACCESS_STREAMING,width,height);
SDL_LockTexture(t, NULL, &pixels, &pitch);
cairo_surface_t *cs=cairo_image_surface_create_for_data(pixels,CAIRO_FORMAT_ARGB32,width,height,pitch);
cairo_t*s=cairo_create(cs);
cairo_t*fr=f(s);SDL_UnlockTexture(t);SDL_RenderCopy(r,t,NULL,NULL);SDL_RenderPresent(r);
return fr;
}
If you want to do a circle or ellipse without 3rd party libraries, include math.h and use the function below I wrote. It will draw aliased ellipse or circles very well. Tested on SDL 2.0.2 and works. It draws one quadrant arc, and mirrors the other arcs, reducing calls to cosf and sinf.
//draw one quadrant arc, and mirror the other 4 quadrants
void sdl_ellipse(SDL_Renderer* r, int x0, int y0, int radiusX, int radiusY)
{
float pi = 3.14159265358979323846264338327950288419716939937510;
float pih = pi / 2.0; //half of pi
//drew 28 lines with 4x4 circle with precision of 150 0ms
//drew 132 lines with 25x14 circle with precision of 150 0ms
//drew 152 lines with 100x50 circle with precision of 150 3ms
const int prec = 27; // precision value; value of 1 will draw a diamond, 27 makes pretty smooth circles.
float theta = 0; // angle that will be increased each loop
//starting point
int x = (float)radiusX * cos(theta);//start point
int y = (float)radiusY * sin(theta);//start point
int x1 = x;
int y1 = y;
//repeat until theta >= 90;
float step = pih/(float)prec; // amount to add to theta each time (degrees)
for(theta=step; theta <= pih; theta+=step)//step through only a 90 arc (1 quadrant)
{
//get new point location
x1 = (float)radiusX * cosf(theta) + 0.5; //new point (+.5 is a quick rounding method)
y1 = (float)radiusY * sinf(theta) + 0.5; //new point (+.5 is a quick rounding method)
//draw line from previous point to new point, ONLY if point incremented
if( (x != x1) || (y != y1) )//only draw if coordinate changed
{
SDL_RenderDrawLine(r, x0 + x, y0 - y, x0 + x1, y0 - y1 );//quadrant TR
SDL_RenderDrawLine(r, x0 - x, y0 - y, x0 - x1, y0 - y1 );//quadrant TL
SDL_RenderDrawLine(r, x0 - x, y0 + y, x0 - x1, y0 + y1 );//quadrant BL
SDL_RenderDrawLine(r, x0 + x, y0 + y, x0 + x1, y0 + y1 );//quadrant BR
}
//save previous points
x = x1;//save new previous point
y = y1;//save new previous point
}
//arc did not finish because of rounding, so finish the arc
if(x!=0)
{
x=0;
SDL_RenderDrawLine(r, x0 + x, y0 - y, x0 + x1, y0 - y1 );//quadrant TR
SDL_RenderDrawLine(r, x0 - x, y0 - y, x0 - x1, y0 - y1 );//quadrant TL
SDL_RenderDrawLine(r, x0 - x, y0 + y, x0 - x1, y0 + y1 );//quadrant BL
SDL_RenderDrawLine(r, x0 + x, y0 + y, x0 + x1, y0 + y1 );//quadrant BR
}
}
My answer extends Scotty Stephens answer by making it a bunch more performant by reducing the API calls to a single one.
// rounding helper, simplified version of the function I use
int roundUpToMultipleOfEight( int v )
{
return (v + (8 - 1)) & -8;
}
void DrawCircle( SDL_Renderer * renderer, SDL_Point center, int radius )
{
// 35 / 49 is a slightly biased approximation of 1/sqrt(2)
const int arrSize = roundUpToMultipleOfEight( radius * 8 * 35 / 49 );
SDL_Point points[arrSize];
int drawCount = 0;
const int32_t diameter = (radius * 2);
int32_t x = (radius - 1);
int32_t y = 0;
int32_t tx = 1;
int32_t ty = 1;
int32_t error = (tx - diameter);
while( x >= y )
{
// Each of the following renders an octant of the circle
points[drawCount+0] = { center.x + x, center.y - y };
points[drawCount+1] = { center.x + x, center.y + y };
points[drawCount+2] = { center.x - x, center.y - y };
points[drawCount+3] = { center.x - x, center.y + y };
points[drawCount+4] = { center.x + y, center.y - x };
points[drawCount+5] = { center.x + y, center.y + x };
points[drawCount+6] = { center.x - y, center.y - x };
points[drawCount+7] = { center.x - y, center.y + x };
drawCount += 8;
if( error <= 0 )
{
++y;
error += ty;
ty += 2;
}
if( error > 0 )
{
--x;
tx += 2;
error += (tx - diameter);
}
}
SDL_RenderDrawPoints( renderer, points, drawCount );
}
A circle of radius 141 would have had 800 SDL_RenderDrawPoint calls in Scottys version, this new version does only execute one single SDL_RenderDrawPoints call, making it much more performant.
One could also strip the rendering portion out of this function, to allow the result to be cached and reused like shown below.
std::vector<SDL_Point> PixelizeCircle( SDL_Point center, int radius )
{
std::vector<SDL_Point> points;
// 35 / 49 is a slightly biased approximation of 1/sqrt(2)
const int arrSize = roundUpToMultipleOfEight( radius * 8 * 35 / 49 );
points.reserve( arrSize );
const int32_t diameter = (radius * 2);
int32_t x = (radius - 1);
int32_t y = 0;
int32_t tx = 1;
int32_t ty = 1;
int32_t error = (tx - diameter);
while( x >= y )
{
// Each of the following renders an octant of the circle
points.push_back( { center.x + x, center.y - y } );
points.push_back( { center.x + x, center.y + y } );
points.push_back( { center.x - x, center.y - y } );
points.push_back( { center.x - x, center.y + y } );
points.push_back( { center.x + y, center.y - x } );
points.push_back( { center.x + y, center.y + x } );
points.push_back( { center.x - y, center.y - x } );
points.push_back( { center.x - y, center.y + x } );
if( error <= 0 )
{
++y;
error += ty;
ty += 2;
}
if( error > 0 )
{
--x;
tx += 2;
error += (tx - diameter);
}
}
return points; // RVO FTW
}
int main()
{
std::vector<SDL_Point> circle = PixelizeCircle( SDL_Point{ 84, 72 }, 79 );
//...
while( true )
{
//...
SDL_RenderDrawPoints( renderer, circle.data(), circle.size() );
//...
}
}
I don't know in which way we can setup a plane surface that is filled with smaller squares (so that I can do better lighting effect).
My code for drawing a single square is:
void drawSquare(float x1, float y1, float x2, float y2) {
glBegin(GL_QUADS);
glVertex3f(x1, y1, 0.0f); // The bottom left corner
glVertex3f(x1, y2, 0.0f); // The top left corner
glVertex3f(x2, y2, 0.0f); // The top right corner
glVertex3f(x2, y1, 0.0f); // The bottom right corner
glEnd();
}
So now how can I run a nested loop to fill the surface with number of smaller squares? I'm a bit unsure about the coordinates of the smaller squares.
Calculate the size of the square and divide it into smaller pieces. Something like this (untested):
void drawSquare(float x1, float y1, float x2, float y2, int xtiles, int ytiles) {
float tile_width = (x2 - x1) / xtiles;
float tile_height = (y2 - y1) / ytiles;
int x, y;
glBegin(GL_QUADS);
for (y = 0; y < ytiles; y++) {
for (x = 0; x < xtiles; x++) {
glVertex3f(x1 + x * tile_width, y1 + y * tile_height, 0.0f); // The bottom left corner
glVertex3f(x1 + x * tile_width, y1 + (y + 1) * tile_height, 0.0f); // The top left corner
glVertex3f(x1 + (x + 1) * tile_width, y1 + (y + 1) * tile_height, 0.0f); // The top right corner
glVertex3f(x1 + (x + 1) * tile_width, y1 + y * tile_height, 0.0f); // The bottom right corner
}
}
glEnd();
}
I created this function that draws a simple polygon with n number of vertexes:
void polygon (int n)
{
double pI = 3.141592653589;
double area = min(width / 2, height / 2);
int X = 0, Y = area - 1;
double offset = Y;
int lastx, lasty;
double radius = sqrt(X * X + Y * Y);
double quadrant = atan2(Y, X);
int i;
for (i = 1; i <= n; i++)
{
lastx = X; lasty = Y;
quadrant = quadrant + pI * 2.0 / n;
X = round((double)radius * cos(quadrant));
Y = round((double)radius * sin(quadrant));
setpen((i * 255) / n, 0, 0, 0.0, 1); // r(interval) g b, a, size
moveto(offset + lastx, offset + lasty); // Moves line offset
lineto(offset + X, offset + Y); // Draws a line from offset
}
}
How can I fill it with a solid color?
I have no idea how can I modify my code in order to draw it filled.
The common approach to fill shapes is to find where the edges of the polygon cross either each x or each y coordinate. Usually, y coordinates are used, so that the filling can be done using horizontal lines. (On framebuffer devices like VGA, horizontal lines are faster than vertical lines, because they use consecutive memory/framebuffer addresses.)
In that vein,
void fill_regular_polygon(int center_x, int center_y, int vertices, int radius)
{
const double a = 2.0 * 3.14159265358979323846 / (double)vertices;
int i = 1;
int y, px, py, nx, ny;
if (vertices < 3 || radius < 1)
return;
px = 0;
py = -radius;
nx = (int)(0.5 + radius * sin(a));
ny = (int)(0.5 - radius * cos(a));
y = -radius;
while (y <= ny || ny > py) {
const int x = px + (nx - px) * (y - py) / (ny - py);
if (center_y + y >= 0 && center_y + y < height) {
if (center_x - x >= 0)
moveto(center_x - x, center_y + y);
else
moveto(0, center_y + y);
if (center_x + x < width)
lineto(center_x + x, center_y + y);
else
lineto(width - 1, center_y + y);
}
y++;
while (y > ny) {
if (nx < 0)
return;
i++;
px = nx;
py = ny;
nx = (int)(0.5 + radius * sin(a * (double)i));
ny = (int)(0.5 - radius * cos(a * (double)i));
}
}
}
Note that I only tested the above with a simple SVG generator, and compared the drawn lines to the polygon. Seems to work correctly, but use at your own risk; no guarantees.
For general shapes, use your favourite search engine to look for "polygon filling" algorithms. For example, this, this, this, and this.
There are 2 different ways to implement a solution:
Scan-line
Starting at the coordinate that is at the top (smallest y value), continue to scan down line by line (incrementing y) and see which edges intersect the line.
For convex polygons you find 2 points, (x1,y) and (x2,y). Simply draw a line between those on each scan-line.
For concave polygons this can also be a multiple of 2. Simply draw lines between each pair. After one pair, go to the next 2 coordinates. This will create a filled/unfilled/filled/unfilled pattern on that scan line which resolves to the correct overall solution.
In case you have self-intersecting polygons, you would also find coordinates that are equal to some of the polygon points, and you have to filter them out. After that, you should be in one of the cases above.
If you filtered out the polygon points during scan-lining, don't forget to draw them as well.
Flood-fill
The other option is to use flood-filling. It has to perform more work evaluating the border cases at every step per pixel, so this tends to turn out as a slower version. The idea is to pick a seed point within the polygon, and basically recursively extend up/down/left/right pixel by pixel until you hit a border.
The algorithm has to read and write the entire surface of the polygon, and does not cross self-intersection points. There can be considerable stack-buildup (for naive implementations at least) for large surfaces, and the reduced flexibility you have for the border condition is pixel-based (e.g. flooding into gaps when other things are drawn on top of the polygon). In this sense, this is not a mathematically correct solution, but it works well for many applications.
The most efficient solution is by decomposing the regular polygon in trapezoids (and one or two triangles).
By symmetry, the vertexes are vertically aligned and it is an easy matter to find the limiting abscissas (X + R cos(2πn/N) and X + R cos(2π(+1)N)).
You also have the ordinates (Y + R sin(2πn/N) and Y + R sin(2π(+1)N)) and it suffices to interpolate linearly between two vertexes by Y = Y0 + (Y1 - Y0) (X - X0) / (X1 - X0).
Filling in horizontal runs is a little more complex, as the vertices may not be aligned horizontally and there are more trapezoids.
Anyway, it seems that I / solved / this myself again, when not relying on assistance (or any attempt for it)
void polygon (int n)
{
double pI = 3.141592653589;
double area = min(width / 2, height / 2);
int X = 0, Y = area - 1;
double offset = Y;
int lastx, lasty;
while(Y-->0) {
double radius = sqrt(X * X + Y * Y);
double quadrant = atan2(Y, X);
int i;
for (i = 1; i <= n; i++)
{
lastx = X; lasty = Y;
quadrant = quadrant + pI * 2.0 / n;
X = round((double)radius * cos(quadrant));
Y = round((double)radius * sin(quadrant));
//setpen((i * 255) / n, 0, 0, 0.0, 1);
setpen(255, 0, 0, 0.0, 1); // just red
moveto(offset + lastx, offset + lasty);
lineto(offset + X, offset + Y);
} }
}
As you can see, it isn't very complex, which means it might not be the most efficient solution either.. but it is close enough.
It decrements radius and fills it by virtue of its smaller version with smaller radius.
On that way, precision plays an important role and the higher n is the less accuracy it will be filled with.