在边缘包裹的世界中寻找行进方向


20

我需要找到从2D世界中的一个点到边缘被包裹的另一点(例如小行星等)的最短距离方向。我知道如何找到最短的距离,但正在努力寻找它的方向。

最短距离由下式给出:

int rows = MapY;
int cols = MapX;

int d1 = abs(S.Y - T.Y);
int d2 = abs(S.X - T.X);
int dr = min(d1, rows-d1);
int dc = min(d2, cols-d2);

double dist = sqrt((double)(dr*dr + dc*dc));

世界的例子

                   :         
                   :  T    
                   :         
    :--------------:---------
    :              :
    :           S  :
    :              :
    :              :
    :  T           :
    :              :
    :--------------:

在图中,边缘用:和-表示。我也已经在右上方显示了环绕的世界重复。我想找到从S到T的度数方向。因此,最短的距离是T的右上角重复。但是,如何计算从S到右上角的重复T的度数方向?

我知道S和T的位置,但是我想我需要找到重复的T的位置,但是那里不止一个。

世界坐标系从左上角的0,0开始,方向可能从West开始的0度。

看起来这应该不太难,但是我还没有找到解决方案。希望有人能帮忙吗?任何网站将不胜感激。


右上角T的坐标是什么?

我从未见过对角线包裹游戏。通常,每个方向(N,E,S,W)都有一个环绕。

5
默认情况下,任何同时具有水平和垂直环绕的游戏都具有对角环绕。

将每个坐标想像成一个圆,然后分别找出每个坐标的两个可能距离中的较短者。
Kerrek SB 2011年

1
@crazy:在Wikipedia上查找“ torus” ...
Kerrek SB 2011年

Answers:


8

您必须稍微调整算法以计算角度-当前,您仅记录位置的绝对差,但是您需要相对差(即,取决于位置,可以是正值或负值)。

int dx = T.X - S.X; // difference in position
int dy = T.Y - S.Y;

if (dx > MapX / 2) // if distance is bigger than half map width, then looping must be closer
    dx = (dx - MapX) * -1; // reduce distance by map width, reverse 
else if (dx < -MapX / 2) // handle the case that dx is negative
    dx = (dx + MapX) * -1;

//Do the same for dy
if (dy > MapY / 2)
    dy = (dy - MapY) * -1;
else if (dy < -MapY / 2)
    dy = (dy + MapY) * -1;

double dist = sqrt(dy*dy+dx*dx); // same as before
double angle = atan2(dy,dx) * 180 / PI; // provides angle in degrees

1
您需要对dx和dy的符号进行一些处理,因为如果TX小于SX或TY小于XY,则代码将中断,这是最好的解决方案,恕我直言。
Scott Chamberlain

那我马上解决。

1
说完所有内容后,关于dx和dy的符号仍然会出现一些错误,请注意我是否进行编辑?
Scott Chamberlain

为什么这是公认的答案?它甚至不起作用。假设MapX是100,T.X是90,S.X是10。dx显然应该是20,但是此算法将返回30!
sam hocevar 2011年

嗯,这是当您不必在发布代码之前进行测试的机会时发生的情况。将修复。如果有人发现另一个错误,我可能会在太多人误导之前将其删除。
Toomai 2011年

11

在这样的世界中,有从S到T的路径无数。让我们分别表示T (Tx, Ty)的坐标,S的坐标(Sx, Sy)和世界的大小(Wx, Wy)。T的包装坐标是(Tx + i * Wx, Ty + j * Wy),其中ij是整数,即set的元素{..., -2, -1, 0, 1, 2, ...}。将S连接到T的向量是(Dx, Dy) := (Tx + i * Wx - Sx, Ty + j * Wy - Sy)。对于给定的(i, j)一对,距离是向量的长度sqrt(Dx * Dx + Dy * Dy),而弧度的方向是atan(Dy / Dx)。的最短路径是路径9,其中的一个ij{-1, 0, 1}在此处输入图片说明

最短路径ij值可以直接确定:

int i = Sx - Tx > Wx / 2 ? 1 : Sx - Tx < -Wx / 2 ? -1 : 0;
int j = Sy - Ty > Wy / 2 ? 1 : Sy - Ty < -Wy / 2 ? -1 : 0;

谢谢@ IlmariKaronen,@ SamHocevar和@romkyns的帮助!


1
您可以做得更好:if abs(Tx-Sx) < Wx/2i=0则为最佳;否则,最佳选择为i=-1i=1,具体取决于的符号Tx-Sx。这同样适用于Ty-Syj
Ilmari Karonen

1
对于这样一个简单的问题,这个答案非常复杂。可以直接计算最小值时,无需使用线性搜索。
sam hocevar

不错的图片,但是建议的算法不值得这个答案收到任何赞誉。
2011年

5

计算一个可能的方向向量,即使它不是最短的也是如此,然后包装其X坐标以使其在[-MapX/2,MapX/2]范围内,并且与Y相同:

int DirX = (T.X - S.X + 3 * MapX / 2) % MapX) - MapX / 2;
int DirY = (T.Y - S.Y + 3 * MapY / 2) % MapY) - MapY / 2;

而已!您也无需进一步计算即可获得距离:

double dist = sqrt((double)(DirX*DirX + DirY*DirY));

谢谢!GLSL版本:vec2 toroidalNearestWay (vec2 from, vec2 to, vec2 mapSize) { return (mod((to - from + 3.0 * mapSize / 2.0), mapSize)) - mapSize / 2.0; }
1j01

0

我想有很多方法可以做到这一点。这是我想到的两个问题:

#1:手动处理案件

可能会发生10种情况:

  • 它与 S
  • 在8个周围的瓷砖中
  • 根本找不到。

但是,对于每个周围的图块,它们都是针对X或Y距离分量的不同计算的排列。由于案例数量有限,因此您可以对如何计算案例进行硬编码,并找出所有案例之间的最短距离。

这是2种寻找案例的说明dx。情况1,其中TSdx 在同一磁贴中S.x - T.x。对于右侧的图块,dx将计算为TileWidth - S.x + T.x

               :         
               :  T    
               :         
:--------------:---------
:              :
:           S  :
:  |--------|--:--|
:dx=(S.x-T.x) dx=(TileWidth-S.x+T.x)
:  T           :
:              :
:--------------:

作为一个小的优化,在求平方根之前找到最小距离。然后,您最多可以保存7个sqrt电话。

#2:提取坐标

如果您需要做一些更加“流畅”的操作,例如寻路算法,只需对坐标进行抽象处理,这样您的寻路算法甚至不会意识到世界是由重复的图块组成的。寻路算法理论上可以在任何方向上无限地前进(好的,您会受到数值限制的限制,但您会明白这一点)。

对于简单的距离计算,请不要这样做。


关于在获取sqrt之前比较平方距离值的明智想法!
Scott Chamberlain

嗯,我知道,@ Kol给出了类似的答案,但给出了更多的数学解释,谢谢这给了我一些东西

比较平方距离可能比平方距更聪明,但是使用曼哈顿距离甚至更聪明,因为它根本不需要乘法。
sam hocevar

0

不要理会“ 9个方向”。原因是在这9个案例中有5个退化案例:“直北”,“直西”,“直南”,“直东”和“相同”。例如,直线北是简并的,因为它代表西北和东北合并并产生相同结果的情况。

因此,您有4个方向可进行计算,并且可以选择最小值。


我认为这不对,或者我完全误解了您。两者之一。

-1

最后,感谢您使用所有由斯科特·张伯伦(Scott Chamberlain)编辑的《 Toomai》的答案。由于我的坐标系从左上角的y开始并随着您向下移动而增加(与y的正常图形坐标相比基本反转),因此我也做了一些更改。

如果有人找到该页面并具有相同的反向系统,我已经发布了。

  int dx = T.X - S.X; // difference in position
int dy = S.Y - T.Y;

if (dx > MapX / 2) // if distance is bigger than half map width, then looping must be closer
    dx = (dx - (MapX / 2)) * -1; // reduce distance by half map width, reverse 
else if (dx < -MapX / 2) // handle the case that dx is negative
    dx = (dx + (MapX / 2)) * -1;

//Do the same for dy
if (dy > MapY / 2)
    dy = (MapY - dy)) * -1;
else if (dy < -MapY / 2)
    dy = (dy + MapY);

double angle = atan2(dy,dx) * 180 / PI; // provides angle in degrees

angle = 180 - angle; //convert to 360 deg

此代码比Toomai的代码略好,但也不起作用。
sam hocevar 2011年

1
另外,您需要了解为什么必须进行这些更改。这不是因为您的坐标系从y顶部开始。这是因为预期的行为应该是在世界边缘包装坐标,而您重用的代码却在每个边界处镜像了坐标。
sam hocevar 2011年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.