在环形包裹的空间上绘制线段

时间:2015-09-06 23:47:34

标签: 2d line distance modulo

我有一个角度为[0, 2pi] x [0, 2pi]的2D空间,它具有环状拓扑(水平边缘相互对应,垂直边缘也是如此)。我在这个空间有两个点,我想在这两个点之间绘制一条线段。

在某些情况下,此线段是明显的线段,从一个点到另一个点。在其他情况下,线段应该绕过边缘"而不是走很长的路,通过中间":

+--------+
|        |
|  A--B  |
|        |
+--------+

+--------+
|        |
|-A    B-|
|        |
+--------+

虽然这些案例容易处理,但有一个案例对我来说真的很烦人,到目前为止我的代码无法正确处理:

+-----------+
|        /  |
|       B   |
|           |
|  A       /|
| /       / |
+-----------+

即。如果线绕两个方向缠绕,它有时会缠绕在对面的角落。我不完全确定是否有更多这些棘手的案件。

到目前为止,我唯一能够可靠运行的算法是将中点计算为(A + B) / 2,同时适当使用模数算术,在此位置绘制一个点,然后递归细分类似地,左和右间隔,直到点之间的距离小于单个像素。显然,这不会很快。

我的另一种方法是(分别针对x和y)检测短距离是直接还是围绕边缘,然后绘制一个或两个线段。这不能正确处理第三种情况,除非该行被分为两部分,并且中点位于示例图像右下角的部分。我不确定如何有效地检测到这一点,或者如何计算中点的位置,因为只是半点中的点并不总是有效,它可能会与其中一个端点一起在边缘处结束,如果它们各自与边缘的距离不相等。

有更好的算法吗?有没有明显的解决方案,我没有看到?我甚至不确定如何谷歌解决这个问题。我不想实现我自己的行光栅化算法,我只想将这个问题分解为Euclidean直线并使用OpenGL或GDI或其他方法绘制它们。

到目前为止我的代码是:

void Draw_WrappedSegment(float f_x0, float f_y0, float f_x1, float f_y1)
{
    const float s = 2 * f_pi;
    f_x0 = fmod(fmod(f_x0, s) + s, s);
    f_y0 = fmod(fmod(f_y0, s) + s, s);
    f_x1 = fmod(fmod(f_x1, s) + s, s);
    f_y1 = fmod(fmod(f_y1, s) + s, s);
    // make sure the coordinates end up being positive and modulo 2pi

    float f_ydist0 = fabs(f_y0 - f_y1);
    float f_ydist1 = fabs(fmod(f_y0 + s - f_y1, s));
    float f_ydist2 = fabs(fmod(f_y1 - f_y0 + s, s));
    float f_xdist0 = fabs(f_x0 - f_x1);
    float f_xdist1 = fabs(fmod(f_x0 + s - f_x1, s));
    float f_xdist2 = fabs(fmod(f_x1 - f_x0 + s, s));
    //     0                        2pi                       4pi
    //p1'' |     p0             p1   |     p0'            p1'  |
    //            <---f_dist0--->
    //                           <-f_dist1->
    // <-f_dist2->

    const float f_epsilon = 1e-3f; // sometimes the modulo causes an error and even though the díst 0 and dist 2 should equal, dist 2 is slightly smaller
    if(f_xdist0 <= f_xdist1 + f_epsilon && f_xdist0 <= f_xdist2 + f_epsilon) {
        if(f_ydist0 <= f_ydist1 + f_epsilon && f_ydist0 <= f_ydist2 + f_epsilon) {
            MoveTo(f_x0, f_y0);
            LineTo(f_x1, f_y1); // the "short" way in both directions
        } else {
            float f_sign = (f_y0 < f_y1)? 1 : -1; // swap the lower and upper edge if the points are not sorted by y
            MoveTo(f_x0, f_y0);
            LineTo(f_x1, f_y1 - f_sign * s); // from point 0 to the lower edge
            MoveTo(f_x1, f_y1);
            LineTo(f_x0, f_y0 + f_sign * s); // from point 1 to the upper edge
        }
    } else {
        if(f_ydist0 <= f_ydist1 + f_epsilon && f_ydist0 <= f_ydist2 + f_epsilon) {
            float f_sign = (f_x0 < f_x1)? 1 : -1; // swap the left and right edge if the points are not sorted by x
            MoveTo(f_x0, f_y0);
            LineTo(f_x1 - f_sign * s, f_y1); // from point 0 to the left edge
            MoveTo(f_x1, f_y1);
            LineTo(f_x0 + f_sign * s, f_y0); // from point 1 to the right edge
        } else {
            float f_sign_x = (f_x0 < f_x1)? 1 : -1; // swap the left and right edge if the points are not sorted by x
            float f_sign_y = (f_y0 < f_y1)? 1 : -1; // swap the lower and upper edge if the points are not sorted by y
            MoveTo(f_x0, f_y0);
            LineTo(f_x1 - f_sign_x * s, f_y1 - f_sign_y * s); // from point 0 to one edge
            MoveTo(f_x1, f_y1);
            LineTo(f_x0 + f_sign_x * s, f_y0 + f_sign_y * s); // from point 1 to the other edge
        }
    }
}

1 个答案:

答案 0 :(得分:1)

不要只使用方格[0, 2pi] x [0, 2pi],而是尝试使用此方块的九个副本(如tic-tac-toe board)平铺空间[-2pi,4pi] x [-2pi,4pi]。将A放在中心正方形,然后在九个正方形的每一个中放置B的副本(根据需要将坐标平移±2pi)。选择最接近A的B的副本,然后将A中的行绘制到B的副本。此行在通过正方形时可能有多个段。只需将这些片段“翻译”回中心广场,您就可以获得所需的图表。