[英]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)结束
算法复杂度: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.