在 space 上绘制环形环绕的线段

Drawing a line segment on a space that wraps toroidally

我有一个 2D space 的角度 [0, 2pi] x [0, 2pi] 环绕,具有类似环形的拓扑结构(水平边缘彼此对应,垂直边缘也是如此)。我在这个space中有两点,我想在这两点之间画一条线段。

在某些情况下,这条线段是明显的线段,从一点到另一点。在其他情况下,线段应该 "go around the edge" 而不是 "the long way, through the middle":

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

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

虽然这些情况比较容易处理,但有一种情况对我来说真的很麻烦,而且我的代码目前无法正确处理:

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

即如果这条线环绕两个方向,它有时会环绕对角。我不确定是否还有更多这些棘手的案例。

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

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

有没有更好的算法?有没有我没有看到的明显解决方案?我什至不确定如何 google 解决这个问题。我不想实现我自己的线光栅化算法,我只想将这个问题分解为欧几里得直线并使用 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
        }
    }
}

与其只使用正方形 [0, 2pi] x [0, 2pi],不如尝试将 space [-2pi,4pi] x [-2pi,4pi] 与该正方形的九个副本(如井字棋盘)平铺在一起。将 A 放在中心方格中,然后将 B 的副本(根据需要将坐标平移 ±2pi)放在九个方格中的每一个方格中。选择最靠近 A 的 B 副本,然后从 A 到 B 的那个副本画一条线。这条线在穿过方块时可能有多条线段。只需 "untranslate" 这些部分回到中心广场,您就会得到想要的图表。