簡體   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