精灵椭圆运动

3

我正在尝试让一个2D精灵以"弧线"(半椭圆)移动,而不是直线。我有起点和终点的X、Y位置,以及所需半径。

最佳实现方法是什么?

4个回答

0

我认为这个问题最好通过一系列坐标变换来解决。为了符号简便,假设你有的两个点是u和v。

假设你正在处理一个非常简单的情况——点u和v分别位于(1,0)和(-1,0),椭圆的长轴长度为1。那么你只需要跟踪半圆。假设你想以恒定的速度在这些点之间插值,你可以使用以下公式:

x(t) = cos(pi * t)
y(t) = sin(pi * t)

当然,你可能没有那么幸运处于这种设置中,因此我们可以进行一系列坐标变换来将你带入这个配置。首先,让我们定义点w为u=(x0,y0)和v=(x1,y1)之间的中点。也就是说:

w = (x2, y2) = ((x0 + x1) / 2, (y0 + y1) / 2)

现在,假设您翻译u和v,使得w位于原点。这意味着u和v沿相反的向量与原点等距离。如果我们使用矩阵和齐次坐标,则可以表示为:

     | 1  0 -x2 |
 T = | 0  1 -y2 |
     | 0  0   1 |

在这个翻译之后,u和v的位置分别由TuTv给出。我们称这些点为u'和v'。它们由以下公式给出:

u' = (x0 - x2, x1 - y2) = (x0 / 2 - x1 / 2, y0 / 2 - y1 / 2)
v' = (x1 - x2, y1 - y2) = (x1 / 2 - x0 / 2, y1 / 2 - y0 / 2)

我们现在更接近解决原始问题了,但是我们有一个问题,即u'和v'与x轴不太对齐,就像在原始问题中一样。为了解决这个问题,我们将应用旋转变换,使得u'最终位于(1,0),v'最终位于(0,1)。为此,我们将建立一个坐标系,在该坐标系中,一个基向量沿着u'的方向,另一个基向量垂直于它。为了做到这一点,我们将选择以下单位向量:

e0 = u' / ||u||
e1 = perp(e0)

其中perp是垂直于e0的某个单位向量。一种获取该向量的方法是,如果e0 = (x3, y3),则e1 = perp(e0) = (-y3, x3)。您可以验证这个向量与(x3, y3)垂直,因为它们的点积为零。

给定这些向量,我们可以定义一个变换,将(1, 0)映射到e0,将(0, 1)映射到e1,如下:

|x3 -y3  0|
|y3  x3  0|
| 0   0  1|

(最后一列是齐次坐标系)

当然,这与我们想要的相反 - 我们试图将e0映射到(1, 0),将e1映射到(0, 1)。为了得到这个矩阵,我们只需反转上面的矩阵。幸运的是,由于我们选择的e0e1是正交的,上面的矩阵是正交的,因此它的逆矩阵就是它的转置:

    | x3 y3 0|
R = |-y3 x3 0|
    |  0  0 1|

现在,如果我们将R应用于u'v',我们最终得到向量(1,0)和(-1,0),这正是我们想要它们的位置。现在的问题是,我们想要跟踪的椭圆不一定具有单位高度。例如,如果我们称其高度为h,那么我们将跟踪一个半长轴为h,半短轴为1的椭圆路径。但是,这可以通过另一个坐标变换轻松纠正,这次通过因子1/h缩放坐标系的高度,使我们想要跟踪的椭圆的高度为1。这可以使用以下缩放矩阵完成:
    | 1  0  0 |
S = | 0 1/h 0 |
    | 0  0  1 |

这种设置之所以有用,是因为我们知道如果我们取所需椭圆上介于uv之间的任意点,然后将矩阵SRT应用于它,那么我们最终会将其转换为使用单位圆上对应点的路径,该路径从(1,0)到(-1,0)。更重要的是,反过来也是一样的。如果我们将单位圆上任意点应用SRT,我们最终会得到原始椭圆路径上介于uv之间的对应点!为了达成协议,我们知道如何找到从(1,0)到(-1,0)路径上的点,因此我们有一个解决此问题的算法:

对于给定的时间t,找到在单位圆上的位置,如果你从(1, 0)移动到(-1, 0)的时间是t。称之为p。 计算p' = (SRT)^-1 p。 p'就是你要找的点。 问题是(SRT)^-1是什么。幸运的是,我们有(SRT)^-1 = T^-1 R^-1 S^-1,而且所有这些矩阵都可以很容易地计算出来:
     | 1  0 -x2 |          | 1  0  x2 |
 T = | 0  1 -y2 |   T^-1 = | 0  1  y2 |
     | 0  0   1 |          | 0  0   1 |

     | x3  y3  0|          | x3 -y3 0 |
 R = |-y3  x3  0|   R^-1 = | y3  x3 0 |
     |  0   0  1|          |  0   0 1 |

     | 1  0   0 |          | 1  0   0 |
 S = | 0 1/h  0 |   S^-1 = | 0  h   0 |
     | 0  0   1 |          | 0  0   1 |

简而言之,最终算法如下:

  1. 给定 u = (x0, y0) 和 v = (x1, y1),令 w = (x2, y2) = ((x0 + x1) / 2, (y0 + y1) / 2)。
  2. 令 u' = u / ||u|| = (x3, y3)。
  3. 在时间 t(对于 0 ≤ t ≤ 1),令 p = (cos(π t), sin(π t))
  4. 计算 p' = S-1p = (cos(π t), h sin(π t))
  5. 计算 p'' = R-1p' = (x3 cos(π t) - y3 sin(π t), y3 cos(π t) + x3 sin(π t))
  6. 计算 p''' = T-1p'' = (x3 cos(π t) - y3 sin(π t) + x2, y3 cos(π t) + x3 sin(π t) + y2)
  7. 将 p''' 输出为您的点。

如果这里有很多数学内容,抱歉了,但是你的答案(希望如此!)应该由上述过程给出。


虽然它描述得很好,但我不知道在哪里可以使用我的半径值来形成一个椭圆曲线而不是一个完美的球体。 - latreides
@latreides- 注意S变换按比例因子h缩放,我理解为椭圆的第二个轴的长度(第一个轴是两个原始点之间的距离)。这不是你想要的吗? - templatetypedef

0
如果你想让它沿椭圆形移动,我所知道的最简单的方法是将y值作为时间的正弦函数,x值作为时间的余弦函数。假设你正在使用System.currentTimeMillis(),你需要将初始时间存储在一个变量中(例如double startTime = System.currentTimeMillis()),然后在每一帧中,通过从当前时间减去开始时间来获取经过的时间(例如elapsedTme = System.currentTimeMillis()-startTime)。然后y值将是(y方向上的半径)*sin(elapsedTime*speed) + 椭圆形中心的y值,x值将是(x方向上的半径)*cos(elapsedTime*speed) + 椭圆形中心的x值。
编辑:如果你有起始的X和Y坐标但没有椭圆形的中心,则我认为最简单的方法是找出其余的变量,然后将它们代入一个方程中以获得中心点。数学应该不会太难。

0

将复杂的维基百科表达式翻译成可用的代码不是我的强项。那个页面包含生成椭圆的JavaScript,如果进行调整可以使用,但我不喜欢每个精灵的每个移动向量都要存储36个左右的点的想法。我希望有一个解决方案,可以让我在任何我想要的椭圆位置上计算X和Y坐标,类似于我在另一个应用程序中使用的一些旧bezier代码。 - latreides

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接