我正在开发一个瓷砖映射游戏 我需要访问光盘中具有给定半径并以给定点为中心的图块。
访问正方形的图块很简单,我们只需要使用两个循环:
for(int i=xmin; i<xmax; ++i)
for(int j=ymin; j<ymax; ++j)
// the tile map[i][j] is in the square
但是如何访问给定光盘中的图块(整圆)?
修改
我的意思是,我可以处理边界矩形(绑定光盘)中的每个图块,并使用(x-x0)²+(y-y0)²<R²
确定该矩形中的图块是否在磁盘中,但是使用该算法,我们会探索无用的瓷砖。
当使用较大的半径时,需要处理许多切片,并且由于多次计算(x-x0)²+(y-y0)²<R²
很重,因此会很慢
我想要的是一种比这更有效的算法。
EDIT2:
我不需要一个完美的磁盘
答案 0 :(得分:6)
我们可以通过x
进行线性扫描,计算y
的范围。然后我们只需要扫描圆圈中的图块,就像在这张画得很糟糕的图片中一样。 (圣诞色?)
如果我们有一个半径为r
且x位置为x
的圆圈,我们可以计算y
的最大长度:
y = sqrt(r * r - x * x);
因此,遍历切片的代码如下所示:
int center_x = (xmin + xmax) / 2;
int center_y = (ymin + ymax) / 2;
for(int x = xmin; x <= xmax; x++) {
int ydist = sqrt(r * r - (center_x - x) * (center_x - x));
for(int y = center_y - ydist; y <= center_y + ydist; y++) {
// these are the tiles in the disc
}
}
这是一些Python代码:
from Tkinter import *
from math import *
tk = Tk()
g = Canvas(tk, width=500, height=500)
g.pack()
x0 = 25 # x center
y0 = 25 # y center
r = 17 # radius
t = 10 # tile side length
for x in range(x0 - r, x0 + r + 1):
ydist = int(round(sqrt(r**2 - (x0 - x)**2), 1))
for y in range(y0 - ydist, y0 + ydist + 1):
g.create_rectangle(x * t, y * t, x * t + t, y * t + t
, fill='#'
+ '0123456789ABCDEF'[15 - int(15 * sqrt((x0 - x)**2 + (y0 - y)**2) / r)]
+ '0123456789ABCDEF'[int(15 * sqrt((x0 - x)**2 + (y0 - y)**2) / r)]
+ '0')
g.create_oval((x0 - r) * t, (y0 - r) * t, (x0 + r) * t + t, (y0 + r) * t + t, outline="red", width=2)
mainloop()
结果磁盘:
最终并不完美,但我希望它对您有效(或者您可以修改它)。
答案 1 :(得分:6)
您可以在瓷砖矩阵中使用Bresenham's circle Algorithm (section 3.3, Scan Converting Circles)(它仅使用整数运算,非常准确并处理整个圆的第四部分以生成整个圆周)来检测形成圆周的那些瓷砖,然后从上到下(或从左到右)跟踪它们之间的线:
以下是circle算法的伪实现:
static void circle(int x0, int y0, int x1, int y1) {
// Bresenham's Circle Algorithm
int x, y, d, deltaE, deltaSE;
int radius, center_x, center_y;
bool change_x = false;
bool change_y = false;
if( x0 > x1 ) {
// swap x values
x = x0;
x0 = x1;
x1 = x;
change_x = true;
}
if( y0 > y1 ) {
// swap y values
y = y0;
y0 = y1;
y1 = y;
change_y = true;
}
int dx = x1 - x0;
int dy = y1 - y0;
radius = dx > dy ? (dy >> 1) : (dx >> 1);
center_x = change_x ? x0 - radius : x0 + radius;
center_y = change_y ? y0 - radius : y0 + radius;
x = 0;
y = radius;
d = 1 - radius;
deltaE = 3;
// -2 * radius + 5
deltaSE = -(radius << 1) + 5;
while(y > x) {
if(d < 0) {
d += deltaE;
deltaE += 2;
deltaSE += 2;
x++;
} else {
d += deltaSE;
deltaE += 2;
deltaSE += 4;
x++;
y--;
}
checkTiles(x, y, center_x, center_y);
}
}
void checkTiles(int x, int y, int center_x, int center_y) {
// here, you iterate tiles up-to-down from ( x + center_x, -y + center_y) to (x + center_x, y + center_y)
// in one straigh line using a for loop
for (int j = -y + center_y; j < y + center_y; ++j)
checkTileAt(x + center_x, j);
// Iterate tiles up-to-down from ( y + center_x, -x + center_y) to ( y + center_x, x + center_y)
for (int j = -x + center_y; j < x + center_y; ++j)
checkTileAt(y + center_x, j);
// Iterate tiles up-to-down from (-x + center_x, -y + center_y) to (-x + center_x, y + center_y)
for (int j = -y + center_y; j < y + center_y; ++j)
checkTileAt(-x + center_x, j);
// here, you iterate tiles up-to-down from (-y + center_x, -x + center_y) to (-y + center_x, x + center_y)
for (int j = -x + center_y; j < x + center_y; ++j)
checkTileAt(-y + center_x, j);
}
使用这种技术,您应该只处理所需的图块(并且仅处理圆的四分之一后),不会检查任何不必要的图块。除此之外,它仅使用整数运算,这使得它非常快(推导和解释可以在提供的书籍链接中找到),并且生成的周长被证明是真实的近似。
答案 2 :(得分:1)
排除广场外的瓷砖不会快得多。我只想使用一个正方形,但忽略圆圈外的瓷砖。 (例如,通过检查拼贴与圆心的距离)
for(int i=xmin; i<xmax; ++i):
for(int j=ymin; j<ymax; ++j):
if map[i][j] not in the circle:
break
// the tile map[i][j] is in the square
对性能开销的粗略估计:
Area Square = 2*r*2*r
Area Circle = pi*r*r
Area Square / Area Circle = 4/pi = 1.27
这意味着使用正方形而不是圆形只是1.27 times slower
(假设使用圆圈没有自己的低效率)
另外,因为您可能会对图块执行某些操作(使得圆圈中的图块的迭代速度慢得多),这意味着使用圆形布局而不是方形布局,性能增益将下降到接近0。
答案 3 :(得分:0)
使用边界八边形。它是角落切割的边界广场。如果一个点(瓷砖的任何一个角落)处于该形状,则需要进行这些测试。把它放在2D循环中。
abs(x) < R
abs(y) < R
abs(x)+abs(y) < sqrt(2)*R
预先计算sqrt(2)* R,当然。
显然,这与圆圈不同,但与正方形相比,可以很好地减少浪费的空间量。
很难生成一个完全覆盖瓷砖中心或瓷砖角落的循环,而不需要在循环中进行某种测试。编写这样的循环的任何希望都来自使用Bresenham的算法。