【发布时间】:2011-06-16 07:26:43
【问题描述】:
我试图让 2d 精灵以“弧”(半椭圆)而不是直线移动。我有 X 和 Y 的开始和结束位置以及所需的半径。
实现这一点的最佳方法是什么?
【问题讨论】:
我试图让 2d 精灵以“弧”(半椭圆)而不是直线移动。我有 X 和 Y 的开始和结束位置以及所需的半径。
实现这一点的最佳方法是什么?
【问题讨论】:
您可能希望使用椭圆的参数形式,此处显示的公式
http://en.wikipedia.org/wiki/Ellipse#General_parametric_form
因为你有一个起始pt,一个结束pt,你需要在两端求解t,
然后在 t 中以相对较小的增量从开始到结束。
【讨论】:
如果您希望它沿椭圆移动,我所知道的最简单的方法是将 y 值作为时间函数与 sin,并将 x 值作为时间函数与 cos。假设您正在使用 System.currentTimeMillis();,您会将初始时间存储在一个变量中(例如 double startTime = System.currentTimeMillis()),然后在每一帧中,您将通过减去当前时间来获得经过的时间开始时间。 (例如 elapsedTme = System.currentTimeMillis()-startTime)。那么 y 值将是(y 方向上的半径)*sin(elapsedTime*speed) + 椭圆中心的 y 值,x 值将是(x 方向上的半径)*cos(elapsedTime*speed) + 椭圆中心的 x 值。
编辑:如果您有起始 X 和 Y 坐标但没有椭圆的中心,那么我认为获得中心的最简单方法就是找出其余变量,然后将它们代入方程.那里的数学应该不会太难。
【讨论】:
我认为这个问题最好通过一系列坐标变换来解决。为了符号简单,我们假设你有两个点是 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的位置由Tu和Tv给出。我们将这些点称为 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)。为了得到这个矩阵,我们只需反转上面的矩阵。幸运的是,由于我们选择了e0和e1作为正交矩阵,所以上面的矩阵是正交的,所以它的逆是它的转置:
| 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 |
此设置有用的原因是我们知道,如果我们在u 和v 之间的所需椭圆上取任意一点,然后将矩阵SRT 应用于它,那么我们最终会转换它使用单位圆上的对应点,即从 (1, 0) 到 (-1, 0) 的路径。不过,更重要的是,这反过来也行得通。如果我们将SRT 的逆应用于单位圆上的任意一点,我们最终会得到u 和v 之间原始椭圆路径上的对应点!为了达成交易,我们知道如何找到从 (1, 0) 到 (-1, 0) 的路径上的点,因此我们有一个算法来解决这个问题:
t,如果您在时间t 从 (1, 0) 移动到 (-1, 0),请找到您在单位圆上的位置。叫它p。p' 是您要找的地方。那么,问题是 (SRT)-1 是什么。幸运的是,我们有 (SRT)-1 = T-1R-1S-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 |
总之,最终算法如下:
对不起,如果这涉及很多数学问题,但您的答案应该(希望!)由上述程序给出。
【讨论】:
我相信您正在寻找贝塞尔曲线,请查看http://www.math.ucla.edu/~baker/java/hoefer/Bezier.htm。来源也可在同一链接中找到。
如果你使用的是SWT,可以查看http://help.eclipse.org/helios/topic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/graphics/GC.html#drawArc(int, int, int, int, int, int)
【讨论】: