首先,您的代码中存在一些错误,因为sin()
和cos()
函数是弧度的,而不是基于度的函数。这将使您的圆出现在伪随机选择的点上,因为每个步骤都与上一个步骤相距约57度。这不会影响现代图形工作的缓冲,您将看到最终结果,而不是工作原理。
如此说来,今天没有人使用您公开的算法画一个圆圈。看一看Bresenham's middle point algorithm,它基本上是试图用八分圆画一个圆,但是要快几倍。
这些算法背后的思想是考虑R^2 = x^2 + y^2
公式,并在一个轴上逐像素绘制,并考虑何时必须遵循另一个轴(您绘制八分圆图,因为这样您就无法应对更大的多于一种衍生品,您只需决定是否向上移动)。该例程还考虑了圆的对称性,以仅计算一个八分圆,然后在每次通过时绘制八个点。
当我年轻的时候(从没见过布雷森汉姆的时候)从头开始开发该算法时,可能我对解决方案的推理会对您有所帮助。
第一次尝试是要考虑到,与大像素相比,小圆圈的分辨率(粒度与 Angular 无关)要少画一些像素,必须重新设计遵循的单度方法以适应更细的像素。粗略的分辨率。这个想法是逐像素而不是逐度进行,直到绘制完整的东西。我们将只绘制第一个八分圆,并将通过图形的对称属性绘制其余八分圆。我们从(0, -R)
点分开,然后逐像素移动,直到到达(sqrt(2)*R, R - sqrt(2)*R)
点。
我们要做的第一件事是尝试保存我们必须做的所有操作。可以保存操作的第一个地方是计算平方...我们将使用R^2 = x^2 + y^2
方程,在该方程上,R
一直仅用作R^2
,因此,假设我们要绘制一个十像素半径的圆,我们会将事物平方到100
(即10像素半径平方)。
接下来,我们将看到正方形的一个属性,即,它们从一个正方形到另一个正方形(0 -> 1(delta is 1) -> 4(delta is 3) -> 9(delta is 5) -> 16(delta is 7) ...
)增长为奇数,因此,如果我们可以安排在x中增加1,我们可以轻松地计算出x ^ 2在odd
变量中添加两个,然后在最后一个平方数上添加odd
,因此我们将使用两个数字:x
和x2
。我们都将其初始化为0
,第一个通过x += 1;
增长,而第二个通过关系x2 += dx2; dx2 += 2;
增长(我们初始化dx2 = 1;
),这使我们仅通过求和而不用乘法就可以允许x
和x2
增长。
如果有人认为我们将需要y2 = 100 - x2
,然后又被迫计算y = sqrt(y2)
,则您的几乎是,但这里的技巧是能够像x一样向后管理y
和y2
序列。好吧,对,y
和y2
可以与x
和x2
一样在相反方向上进行管理,但是这次我们必须倒退,将(what?)的奇数减少到1
的dy2 -= 2; y2 -= dy2;
,最后到达0
。为此,请检查两个连续的平方之间的差是否恰好是两个数字的乘积,例如,13^2 = 169
和14^2 = 196
之间的差为13 + 14 = 27
,如果从R = 14
返回到0
中的y
。
之所以这么复杂,是因为我们只用整数进行加法,而无需进行乘法运算(嗯,乘法运算并不那么昂贵,但是有一段时间是这样)。半径R
,但在开始计算R^2
时只执行一次。
现在的想法是在出发点(0, -R)
处设置原点,然后逐像素向右移动,添加(并修改)x
,x2
和sum
(我们一直减去总和),直到到达下一个平方y
,然后更新所有y轴值(我们要减小y,我们必须在那一刻向上移动一个像素)y
,y2
,dy2
,并绘制像素(或之前绘制它,如我们在例程中所做的那样) ),直到...什么? (好一点,直到我们在完成八分圆的45度点相遇,x
和y
坐标相等为止),重要的是到此为止,因为从该点开始,可能有一个步骤使y坐标以增加一个以上像素(导数大于1),这将使总体算法复杂化(无论如何,我们要绘制其他对称的八个点,因此要绘制图形的另一部分)
因此,假设我们有100作为半径,并开始于:
x=0, x2= 0, dx2= 1, y=10, y2=100, dy2=19, sum=100 *
x=1, x2= 1, dx2= 3, y= 9, y2= 81, dy2=17, sum= 99
x=2, x2= 4, dx2= 5, y= 9, y2= 81, dy2=17, sum= 96
x=3, x2= 9, dx2= 7, y= 9, y2= 81, dy2=17, sum= 91
x=4, x2=16, dx2= 9, y= 9, y2= 81, dy2=17, sum= 84 *
x=5, x2=25, dx2=11, y= 8, y2= 64, dy2=15, sum= 75 *
x=6, x2=36, dx2=13, y= 7, y2= 49, dy2=13, sum= 64 *
x=7, x2=49, dx2=15, y= 7, y2= 49, dy2=13, sum= 51
标有星号的点是
sum
值与下一个
y2
值相交的点,这会使
y
值递减,并且必须移动我们绘制的像素。最后的例程是:
int bh_onlyonepoint(int r, int cx, int cy)
{
int r2 = r*r;
int x = 0, x2 = 0, dx2 = 1;
int y = r, y2 = y*y, dy2 = 2*y - 1;
int sum = r2;
while(x <= y) {
draw(cx + x, cy + y); /* draw the point, see below */
sum -= dx2;
x2 += dx2;
x++;
dx2 += 2;
if (sum <= y2) {
y--; y2 -= dy2; dy2 -= 2;
}
} /* while */
return x; /* number of points drawn */
}
如果需要,我编写了一个简单的示例,以半径作为命令参数,在屏幕上以ascii形式绘制一个圆。它使用ANSI转义序列在绘制单个星号之前将光标定位在屏幕上。缩放比例在X方向上加倍以补偿字符高度(ascii中的“像素”不为正方形)我包括一个新的绘制函数指针参数以调用点绘制,以及一个
main
例程以从命令行获取参数。 :
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void draw(int x, int y)
{
/* move to position (2*x, y) and plot an asterisk */
printf("\033[%d;%dH*", y, x<<1);
}
int bh(int r, int cx, int cy, void(*draw)(int, int))
{
/* the variables mentioned in the text */
int r2 = r*r;
int x = 0, x2 = 0, dx2 = 1;
int y = r, y2 = y*y, dy2 = 2*y - 1;
int sum = r2;
while(x <= y) {
/* draw the eight points */
draw(cx + x, cy + y);
draw(cx + x, cy - y);
draw(cx - x, cy + y);
draw(cx - x, cy - y);
draw(cx + y, cy + x);
draw(cx + y, cy - x);
draw(cx - y, cy + x);
draw(cx - y, cy - x);
sum -= dx2;
x2 += dx2;
x++;
dx2 += 2;
if (sum <= y2) {
y--; y2 -= dy2; dy2 -= 2;
}
} /* while */
return x; /* number of points drawn */
}
int main(int argc, char **argv)
{
int i;
char *cols = getenv("COLUMNS");
char *lines = getenv("LINES");
int cx, cy;
if (!cols) cols = "80";
if (!lines) lines = "24";
cx = atoi(cols)/4;
cy = atoi(lines)/2;
printf("\033[2J"); /* erase screen */
for (i = 1; i < argc; i++) {
bh(atoi(argv[i]), cx, cy, draw);
}
fflush(stdout);
sleep(10);
puts(""); /* force a new line */
}
最终结果是:
*
* * * * * * * * * *
* * * *
* *
* * * *
* * *
* * * * * * * * * *
* * * *
* * * * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * * * *
* * * *
* * * * * * * * * *
* * *
* * * *
* *
* * * *
* * * * * * * * * *
*
最后,如果您想获得更好的结果(不是由半径的确切值引起的那些仅使它们在x或y为零时接触点的峰),则可以直接将例程传递给半径的平方值(这样可以使分数半径的整数计算)
填补一个圆圈
如果要填充圆,只需绘制一对计算点之间的所有点即可,如下所示:
lineFromTo(cx - x, cy - y, cx + x, cy - y);
lineFromTo(cx - y, cy + x, cx + y, cy + x);
lineFromTo(cx - y, cy - x, cx + y, cy - x);
lineFromTo(cx - x, cy + y, cx + x, cy + y);
这些都是水平线,因此也许您可以通过以下方式获得改进:
/* X1 X2 Y */
HorizLineX1X2Y(cx - x, cx + x, cy - y);
HorizLineX1X2Y(cx - y, cx + y, cy + x);
HorizLineX1X2Y(cx - y, cx + y, cy - x);
HorizLineX1X2Y(cx - x, cx + x, cy + y);
已在
github中创建了一个新的git存储库,其最终程序允许填充,绘制或跟踪算法的运行
我是一名优秀的程序员,十分优秀!