edu*_*unn 1 c++ graphics geometry sdl-2
我是在正确的轨道上,用SDL2绘制一个圆圈吗?我认为使用参数方程和趋于零的半径将起作用,但就处理器使用而言似乎效率非常低.非常感谢任何其他想法提前感谢.
//Circle test
int circle_x = WINDOW_WIDTH/2;
int circle_y = WINDOW_HEIGHT/2;
int circle_radius = 100;
SDL_SetRenderDrawColor(window.renderer, 100, 100, 255, 255);
int point_x;
int point_y;
while (circle_radius > 0)
{
for (int t = 0; t < 360; t++)
{
point_x = circle_x + circle_radius * cos(t);
point_y = circle_y + circle_radius * sin(t);
SDL_RenderDrawPoint(window.renderer, point_x, point_y);
}
circle_radius--;
}
Run Code Online (Sandbox Code Playgroud)
首先,你的代码中存在一些错误,因为sin()和cos()函数是弧度而不是基于度数的函数.这将使您的圆圈出现在伪随机选择的点上,因为每个步骤绘制一个与前一个点相差大约57度的点.这将没有影响,因为现代图形工作缓冲,你会看到最终的结果,而不是工作.
一旦说到这一点,今天没有人使用你的暴露算法画一个圆圈.看看Bresenham的中间点算法,它基本上试图用八分圆绘制一个圆,但要快几倍.
这些算法背后的想法是考虑R^2 = x^2 + y^2公式并在其中一个轴上逐个像素地绘制,并考虑何时必须遵循另一个轴(您通过八分圆绘制,因为这样您不应该处理大于一个衍生品,你只需决定你是否向上移动).例程还考虑了圆对称性,仅计算一个八分圆,然后在每次通过时绘制八个点.
当我年轻的时候从头开始开发那个算法(之前没见过Bresenham)我可能对解决方案的推理对你有所帮助.
第一次尝试是考虑到小圆圈的分辨率(粒度不依赖于角度),你必须绘制比大光圈更少的像素,你必须重新设计你所遵循的一度方法以适应更精细或粗略的决议.我的想法是逐个像素地,而不是逐个程度地去,直到你绘制完整的东西.我们将仅绘制第一个八分圆,并将通过图的对称属性绘制其余部分.我们从这(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但这一次我们要往回走,从(什么?),以减少奇数1其中dy2 -= 2; y2 -= dy2;,最终达到0.为了做到这一点,检查连续两个方块之间的差别恰恰增加了两个数字,因此,举例来说,差异之间13^2 = 169,并14^2 = 196是13 + 14 = 27,这是奇数开始与如果我们回到从R = 14到0在y.
使事情变得如此复杂的原因是,这种方式我们只用整数进行加法而不需要进行乘法运算(好的,乘法并不是那么昂贵,但是有一段时间它们是这样的)好吧,我们做乘法到最初的平方半径R,但我们只在开始时做一次计算R^2.
现在的想法是设置原点出发(0, -R),去正确的,逐像素,加入(和modifiying) 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
Run Code Online (Sandbox Code Playgroud)
标有星号的点是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 */
}
Run Code Online (Sandbox Code Playgroud)
如果你愿意,我写了一个简单的例子来在屏幕上的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 */
}
Run Code Online (Sandbox Code Playgroud)
最终的结果是:
*
* * * * * * * * * *
* * * *
* *
* * * *
* * *
* * * * * * * * * *
* * * *
* * * * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * *
* * * * * *
* * * *
* * * * * * * * * *
* * *
* * * *
* *
* * * *
* * * * * * * * * *
*
Run Code Online (Sandbox Code Playgroud)
最后,如果你想得到更好的结果(不是那些由精确的半径值引起的峰值,使它们只触及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);
Run Code Online (Sandbox Code Playgroud)
这些都是水平线,所以也许你可以通过以下方式获得改进:
/* 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);
Run Code Online (Sandbox Code Playgroud)
在github中创建了一个新的git存储库,其中包含允许填充,绘制或跟踪算法运行的最终程序