我有一条从(x0,y0)到(x1,y1)的线穿过由2 ^ n宽的方形瓷砖制成的网格。我不仅需要找到线相交的瓦片,还需要找到相应的入口和出口点。关于这一切的所有SO问题我可以找到处理“1x1”瓷砖而不关心瓷砖内交叉点的位置。
积分并不总是精确地在一个整数上,在某些情况下我会使用自然地板和其他我想要整理的地方。但是现在让它在所有情况下都能自然发挥作用。但
I found an example最终得到了一个非常简单的使用整数进行光线跟踪的情况,但它并没有跟踪交点,也不适用于通过中心的线条(假设为0.5, 0.5x偏移)的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个相关交叉点?似乎能够在一块瓷砖中“随处”启动的能力确实会破坏这个算法,而我的解决方案最终会使用除法,并且可能会在每次迭代时累积误差。这不好......
此外,我认为对于第一个和最后一个图块,端点可以被假定为“其他”交叉点。
答案 0 :(得分:1)
Woo,Amanatides有一篇有用的文章“Fast Voxel Traversal Algorithm...”。 看一下实际的实现(grid traversal section)。 我用这种方法效果很好。
答案 1 :(得分:1)
通过将整个坐标系除以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)。
答案 2 :(得分:0)
在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坐标都是整数),那么相同的交点将被添加到列表中。 2 for 循环。在这种情况下,您可能希望从列表中删除重复的交叉点。