繁体   English   中英

以整数形式获取线段和2 ^ n网格之间的所有交叉点

[英]Getting all intersection points between a line segment and a 2^n grid, in integers

我有一条从(x0,y0)到(x1,y1)的线穿过由2 ^ n宽的正方形瓷砖制成的网格。 我不仅需要找到线相交的瓦片,还需要找到相应的入口和出口点。 关于这一切的所有SO问题我可以找到处理“1x1”瓷砖而不关心瓷砖内交叉点发生的位置。

在此输入图像描述

积分并不总是精确地在一个整数上,在某些情况下我会使用自然地板和其他我想要整理的地方。 但是现在让它在所有情况下都自然而然地落地。

我找到了一个例子 ,最终得到了一个非常简单的使用整数进行光线跟踪的情况,但是它并没有跟踪交点,也没有适用于通过中心的线条(假设为0.5,0.5偏移) 1x1瓷砖。

void raytrace(int x0, int y0, int x1, int y1)
{
    int dx = abs(x1 - x0);
    int dy = abs(y1 - y0);
    int x = x0;
    int y = y0;
    int n = 1 + dx + dy;
    int x_inc = (x1 > x0) ? 1 : -1;
    int y_inc = (y1 > y0) ? 1 : -1;
    int error = dx - dy;
    dx *= 2;
    dy *= 2;

    for (; n > 0; --n)
    {
        visit(x, y);

        if (error > 0)
        {
            x += x_inc;
            error -= dy;
        }
        else
        {
            y += y_inc;
            error += dx;
        }
    }
}

如何才能找到相交的2 ^ nx 2 ^ n网格图块,同时抓住2个相关的交叉点? 似乎能够在一块瓷砖中“随处”启动的能力确实会破坏这个算法,而我的解决方案最终会使用除法,并且可能会在每次迭代时累积误差。 这不好......

另外我认为对于第一个和最后一个图块,端点可以被假定为“其他”交叉点。

Woo,Amanatides有一篇有用的文章“ Fast Voxel Traversal Algorithm ... ”。 看一下实际的实现( 网格遍历部分 )。 我用这种方法效果很好。

通过将整个坐标系除以2 ^ n,可以将2 ^ n X 2 ^ n平铺大小减小到1 X 1

确切地说,在我们的情况下,这意味着您将线的起点和终点的坐标除以2 ^ n。 从现在开始,您可以将问题视为1X1大小的磁贴问题。 在问题的最后,我们将2 ^ n乘以我们的解决方案,以获得2 ^ n X 2 ^ n解决方案的答案。

现在是在每个瓷砖中找到进入和退出点的部分。 假设线从(2.4,4.6)开始到(7.9,6.3)结束

  • 由于线的起点和终点的x坐标是2.4和7.9,因此,它们之间的所有整数值都将与我们的线相交,即x坐标为3,4,5,6,7的瓦片将相交。 我们可以使用输入线的等式计算这些x坐标的相应y坐标。
  • 类似地,线的起点和终点的y坐标之间的所有整数将导致线和瓦片之间的另一组交叉点。
  • 根据x坐标对所有这些点进行排序。 现在成对挑选它们,第一个将是入口点,第二个将是出口。
  • 将这些点乘以2 ^ n,以获得原始问题的解决方案。

算法复杂度:O(nlog n)其中n是行的起始坐标和结束坐标之间的整数范围。 通过微小的修改,这可以进一步减少到O(n)。

在x0..x1范围内插入x的每个整数值,并求解每个y。 这将为您提供瓷砖两侧交叉点的位置。

在y0..y1范围内插入y的每个整数值,并求解x。 这将为您提供瓷砖顶部/底部交叉点的位置。

编辑

在处理不同的瓷砖尺寸并从瓷砖内部开始时,代码会变得更加丑陋,但想法是一样的。 这是C#中的解决方案(在LINQPad中按原样运行):

List<Tuple<double,double>> intersections = new List<Tuple<double,double>>();

int tile_width = 4;

int x0 = 3;
int x1 = 15;
int y0 = 1;
int y1 = 17;

int round_up_x0_to_nearest_tile = tile_width*((x0 + tile_width -1)/tile_width);
int round_down_x1_to_nearest_tile = tile_width*x1/tile_width;

int round_up_y0_to_nearest_tile = tile_width*((y0 + tile_width -1)/tile_width);
int round_down_y1_to_nearest_tile = tile_width*y1/tile_width;

double slope = (y1-y0)*1.0/(x1-x0);
double inverse_slope = 1/slope;

for (int x = round_up_x0_to_nearest_tile; x <= round_down_x1_to_nearest_tile; x += tile_width)
{
    intersections.Add(new Tuple<double,double>(x, slope*(x-x0)+y0));
}

for (int y = round_up_y0_to_nearest_tile; y <= round_down_y1_to_nearest_tile; y += tile_width)
{
    intersections.Add(new Tuple<double,double>(inverse_slope*(y-y0)+x0, y));
}

intersections.Sort();

Console.WriteLine(intersections);

这种方法的缺点是,当线条恰好在一个角上与一个瓷砖相交时(即交点的x和y坐标都是整数),那么相同的交点将被添加到列表中的每一个中循环。 在这种情况下,您需要从列表中删除重复的交叉点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM