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

Posted

技术标签:

【中文标题】以整数形式获取线段和 2^n 网格之间的所有交点【英文标题】:Getting all intersection points between a line segment and a 2^n grid, in integers 【发布时间】:2012-12-14 18:19:30 【问题描述】:

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

这些点并不总是精确地在一个整数上,在某些情况下,我会使用自然地板,而在其他情况下,我会想四舍五入。但是现在让它在所有情况下都自然下降是可以的。

I found an example 最终得到了一个非常简单的整数光线追踪案例,但它不跟踪交点,也不适用于穿过中心的线(假设 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^n x 2^n 网格图块,同时还能抓取 2 个相关的交叉点?似乎能够在图块中的“任何地方”开始确实破坏了这个算法,我的解决方案最终使用除法,并且可能设置了在每次迭代中累积错误的东西。这也不好……

我还认为对于第一个和最后一个图块,可以假设端点是“其他”交点。

【问题讨论】:

如果你有 1x1 关系的解决方案,为什么不把你的问题除以 2^n,解决它,然后再转换回来? 因为解决方案不跟踪图块中交叉点的位置。它也不支持从瓷砖的“中心”以外的任何地方开始,这真的让我很困惑。 你检查过布雷森纳姆吗? en.wikipedia.org/wiki/Bresenham's_line_algorithm 相关:***.com/questions/13700625/… 对不起,我应该仔细阅读。 【参考方案1】:

Woo, Amanatides 有一篇有用的文章“Fast Voxel Traversal Algorithm...”。 看看实际的实现(grid traversal section)。 这个方法我用过,效果不错。

【讨论】:

【参考方案2】:

您可以通过将整个坐标系除以 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)。

【讨论】:

【参考方案3】:

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

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

编辑

在处理不同的 tile 大小并从 tile 内部开始时,代码会变得有点难看,但想法是一样的。这是 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 坐标都是整数),那么相同的交叉点将被添加到列表中2 个 for 循环。在这种情况下,您需要从列表中删除重复的交点。

【讨论】:

以上是关于以整数形式获取线段和 2^n 网格之间的所有交点的主要内容,如果未能解决你的问题,请参考以下文章

[CodeForces-1036E] Covered Points 暴力 GCD 求交点

POJ - 1127 Jack Straws(几何)

[Heoi2013]Segment

所有线段的交点-初级篇

所有线段的交点-初级篇

[bzoj3165] [HEOI2013]Segment