用于访问光盘中矩阵(游戏地图)中的图块的算法

时间:2012-12-25 23:10:52

标签: algorithm

我正在开发一个瓷砖映射游戏 我需要访问光盘中具有给定半径并以给定点为中心的图块。

访问正方形的图块很简单,我们只需要使用两个循环:

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:
我不需要一个完美的磁盘

4 个答案:

答案 0 :(得分:6)

我们可以通过x进行线性扫描,计算y的范围。然后我们只需要扫描圆圈中的图块,就像在这张画得很糟糕的图片中一样。 (圣诞色?)

enter image description here

如果我们有一个半径为r且x位置为x的圆圈,我们可以计算y的最大长度:

enter image description here

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()

结果磁盘:

enter image description here

最终并不完美,但我希望它对您有效(或者您可以修改它)。

答案 1 :(得分:6)

您可以在瓷砖矩阵中使用Bresenham's circle Algorithm (section 3.3, Scan Converting Circles)(它仅使用整数运算,非常准确并处理整个圆的第四部分以生成整个圆周)来检测形成圆周的那些瓷砖,然后从上到下(或从左到右)跟踪它们之间的线:

enter image description here

以下是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的算法。