以整数形式获取线段和 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 网格之间的所有交点的主要内容,如果未能解决你的问题,请参考以下文章