计算 2 个移动球将发生碰撞的 x/y 点
我正在尝试制作一个(本质上)简单的台球游戏,并且希望能够预测一旦击中另一个球就会去哪里。
我认为,第一部分是计算母球是否会击中任何物体,如果击中,则碰撞在哪里。我可以算出一条线和一个球的碰撞点,但不能算出两个球的碰撞点。
那么给定 2 个球的 x/y 位置和速度,我如何计算它们碰撞的点?
(PS:我知道我可以通过计算沿途每一步两个球之间的距离来做到这一点,但我希望有一些更优雅和更优化的东西。)
设置示例:尝试计算红点
I'm trying to make what is (essentially) a simple pool game, and would like to be able to predict where a shot will go once it hits another ball.
The first part is, I believe, to calculate if the cueball will hit anything, and if it does, where it collides. I can work out collision points for a line and a ball, but not 2 balls.
So given the x/y positions and velocities of 2 balls, how do I calculate the point at which they collide?
(PS: Im aware I can do this by calculating the distance between the two balls every step along the way, but I was hoping for something a little more elegant and optimal.)
Example of setup: trying to calculate the red dot
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
需要注意的一些事项:
r
的球发生碰撞时,它们的中心相距2r
。现在您需要做一些几何工作。
进行此构造:
A
。AB
。A
沿运动方向构造射线R
。B
构建一个半径为2r
的圆。B
垂直于R
放下一条线段,称为交点C
。AB
,并且可以根据正弦定理找到AB
和R
之间的角度alpha
求BC
的长度。R
交汇处构造点D
,并再次使用正弦定理求出距离 AD。BD
的中点,现在你知道了一切。
由此构建有效的代码作为练习。
顺便说一句——如果两个球都在移动,这种结构就不起作用,但你可以转变成一个静止的框架,以这种方式解决它,然后再转回来。只要确保在逆向变换之后检查解是否在允许的区域内...
/物理学家不能不发表这样的评论。我试图反抗。我真的做到了。
Some things to take note of:
r
collide their centers are2r
apart.alpha
between this path and the direction from the first ball to the second.Now you have some geometry to do.
Do this construction:
A
.B
.AB
.R
, fromA
in the direction of movement.2r
aroundB
.B
perpendicular toR
call the point of intersectionC
.AB
and you can find the anglealpha
betweenAB
andR
, with the Law of Sines find the length ofBC
.D
where the circle meetsR
closer to A, and use the Law of Sines again to find the distance AD.BD
and now you know everything.
Constructing efficient code from this is left as an exercise.
BTW-- This construction won't work if both balls are moving, but you can transform into a frame where one is stationary, solve it that way, then transform back. Just be sure to check that the solution is in the allowed area after the reverse transformation...
/ Physicists can't not make comments like this. I tried to resist. I really did.
@dmckee 的答案图
编辑
只是为了回应@ArtB necromancer的回答,上图中D点的解决方案可以写成:
Where
And
HTH!
Drawing of @dmckee's answer
Edit
Just in response to @ArtB necromancer's answer, the solutions for point D in the above graph could be written:
Where
And
HTH!
我正在查看 @dmckee 的解决方案,我花了很多功夫才完成它。以下是我的笔记,供那些可能正在寻找更实际答案的人使用,直接采取
来自他的帖子,因此功劳归于他/她,但任何错误都是我的。我通常使用类似 Pascal 的赋值运算符(即
:=
)来区分显示我的工作和实际必要的代码。我使用标准
Y = mX +b
格式和准oop表示法。我确实对段和结果线使用 BC。也就是说,这应该“几乎”是可复制粘贴的 Python 代码(删除“;”,用正确的版本替换“sqrt”和“sqr”等)。
AB.m := (by - ay) / (bx - ax);
AB.b := Ay - AB.m * Ax;
Rm := Avy / Avx;
Rb := Ay - Rm * Ax;
BC.m := -Avx/Avy;
这是垂直斜率的标准方程,BC.b := By - BC.m * Bx;
现在C
是AB
满足 < code>BC 所以我们知道它们是相等的,所以让Cy
相等,所以Cy == AB.m * Cx + AB.b == BC.m * Cx + BC .b;
所以Cx := (AB.m - BC.M) / (BC.b - AB.b);
然后插入Cx
得到Cy := AB.m * Cx + AB.b;
的长度BC
,BC.l := sqrt( sqr(Bx-Cx) + sqr(By-Cy) );
BC.l > Ar + Br
,解为零,并且这些圆不接触,因为C
是A
的路径相对于
的近地点代码>B<代码>。如果BC.l == Ar + Br,则只有一个解,
C == D。否则,如果 BC.l < Ar + Br
然后就有
A`的就是我们想要的。有两种解决方案。你可以这样想,如果子弹错过了零个解决方案,如果有一个子弹擦过,如果有两个,则同时存在入口和出口伤口。更接近
D
是AC
上距离 BAr + Br
(又名2r
)的点,因此:sqrt( sqr(Dx - Bx) + sqr(Dy - By) ) == 2r
sqr(Dx - Bx) + sqr(Dy - By) == 4*r*r
。现在 2 个变量(即Dx
和Dy
)与一个方程是很麻烦的,但我们也知道D
是在
AC
线上,所以Dy == AC.m*Dx + AC.b
。Dy
,给出sqr(Dx - Bx) + sqr(AC.m*Cx + AC.b - By) == 4*r*r
.sqr(Dx) + 2*Dx - sqr(Bx) + sqr(AC.m*Dx) + 2*AC.b*Dx - 2*AC.m*Dx *By + sqr(AC.b) - 2*AC.b*By + sqr(By) == 4*r*r
(这是我最有可能制作的部分如果我确实犯了一个错误)。Dx
未知;其余的我们可以将它们视为常量)以获得sqr(Dx) + 2*Dx - sqr(Bx) + sqr(AC.m*Dx) + 2*AC.b*Dx - 2*AC.m*Dx*By + sqr(AC.b) - 2*AC.b*By + sqr(By) == 4*r*r
<代码>(sqr(Dx) + sqr(AC.m*Dx)) + ( 2*Dx + 2*AC.b*Dx - 2*AC.m*By*Dx ) + ( - sqr(Bx) + sqr(AC.b) - 2*AC.b*By + sqr(By) ) == 4*r*r 可以重构为
<代码>(1 + sqr(AC.m)) * sqr(Dx) + 2*( 1 + AC.b - AC.m*By ) * Dx + ( sqr(By) - sqr(Bx) + sqr( AC.b) - 2*AC.b*By - 4*r*r ) == 0
x == (-bb +- sqrt( sqr(bb) - 4*aa*cc ) / 2*aa
)(使用aa
以避免与之前的变量混淆),使用aa := 1 + sqr(AC.m);
,bb := 2*( 1 + AC.b - AC.m*By );
和cc := sqr(By) - sqr(Bx) + sqr(AC.b) - 2*AC.b*By - sqr(A.r+ Br);
.-bb/2aa +- sqrt(sqr(bb)-4*aa*cc)/2*aa
保存部分:first_term := -bb/(2*a);
和second_term := sqrt(sqr(bb)-4*aa*cc)/2*aa;
。D1.x = first_term + secondary_term;
和D1.y = AC.m * D1.x + AC.b
,以及第二个解决方案D2.x = 第一个项 + 第二个项;
与D2.y = AC.m * D2.x + AC.b
。A
的距离:D1.l := sqrt( sqr(D1.xA.x) + sqr(D1.yA.y) );
以及D2.l = sqrt( sqr(D2.xA.x) + sqr(D2.yA.y) );
(实际上跳过两个平方根效率更高,但没有区别) .D := D1 if D1.l < D2。 l 否则D2;
。DB
的中点,我们称之为 E,就是碰撞(如果半径不相等,我不知道这如何概括)。DB.m := (By-Dy)/(Bx-Dx);
和DB.b = By - DB.m*Bx;
。BD.l == Ar + Br
,因此sqrt( sqr(Ex-Bx) + sqr(Ey -By) ) == Br
.E
,因为我们知道它在BD
上,所以Ey == BD.m * Ex + BD.b
,得到sqrt( sqr(Ex-Bx) + sqr(BD.m * Ex + BD.bB.y) ) == Br
。sqr(Ex) - 2*Ex*Bx + sqr(Bx) + sqr(BD.m)*sqr(Ex) + 2*BD.m*Ex*BD.b - 2* BD.m*By + sqr(By) - 2*BD.b*By + sqr(By) == sqr(Br)
sqr(Ex) + sqr(BD.m)*sqr(Ex) + 2*BD.m*Ex*BD.b - 2*Ex*Bx + sqr(Bx) - 2*BD。 m*By + sqr(By) - 2*BD.b*By + sqr(By) == sqr(Br)
<代码>(1 + sqr(BD.m)) * sqr(Ex) + 2*(BD.m*BD.b - Bx) * Ex + sqr(Bx) + sqr(By) - 2*BD.m *By + sqr(By) - 2*BD.b*By + sqr(By) - sqr(Br) == 0
aa := (1 + sqr(BD.m)); bb := 2*(BD.m*BD.b - Bx); cc := sqr(By) - 2*BD.m*By + sqr(By) - 2*BD.b*By + sqr(By) - sqr(Br);,二次公式,然后得到你的两个点并选择更接近 B 的一个。
嫖娼,但我真的需要解决这个问题并认为我会分享。呃,我想我现在需要躺下。
I was looking at @dmckee's solution and it took me quite a lot of work to work through it. The following is my notes for those perhaps looking for a more practical answer, it is taken directly
from his post so the credit goes to him/her, but any mistakes are mine. I usual a Pascal-like assigment operator (ie
:=
) to distinguish between showing my work and the actual necessary code.I'm using the standard
Y = mX +b
format and a quasi-oop notation. I do use BC for both the segment and the resulting line. That said, this should 'almost' be copy'n'paste-able Python code(remove ";", replace "sqrt" and "sqr" with proper versions, etc).
AB.m := (b.y - a.y) / (b.x - a.x);
AB.b := A.y - AB.m * A.x;
R.m := A.v.y / A.v.x;
R.b := A.y - R.m * A.x;
BC.m := -A.v.x/A.v.y;
which is the standard equation for perpendicular slope,BC.b := B.y - BC.m * B.x;
NowC
is whereAB
meetsBC
so we know that they are equal so lets equateC.y
soC.y == AB.m * C.x + AB.b == BC.m * C.x + BC.b;
soC.x := (AB.m - BC.M) / (BC.b - AB.b);
then just plug inC.x
to getC.y := AB.m * C.x + AB.b;
BC
,BC.l := sqrt( sqr(B.x-C.x) + sqr(B.y-C.y) );
BC.l > A.r + B.r
, there are zero solutions, and these circles do not touch sinceC
isA
's paths perigee with respect to
B. If
BC.l == A.r + B.r, there is only one solution, and
C == D. Otherwise, if
BC.l < A.r + B.rthen there
A` is the one we want.are two solutions. You can think of this as such, if there are zero solutions the bullet missed, if there is one the bullet grazed, and if there are two then there is both an entry and exit wound. The one closer to
D
is a point onAC
that isA.r + B.r
(aka2r
) away from B so:sqrt( sqr(D.x - B.x) + sqr(D.y - B.y) ) == 2r
sqr(D.x - B.x) + sqr(D.y - B.y) == 4*r*r
. Now 2 variables (ieD.x
andD.y
) with one equation is trouble, but we also know thatD
ison the line
AC
soD.y == AC.m*D.x + AC.b
.D.y
givingsqr(D.x - B.x) + sqr(AC.m*C.x + AC.b - B.y) == 4*r*r
.sqr(D.x) + 2*D.x - sqr(B.x) + sqr(AC.m*D.x) + 2*AC.b*D.x - 2*AC.m*D.x*B.y + sqr(AC.b) - 2*AC.b*B.y + sqr(B.y) == 4*r*r
(this is the part where I mostly likely made a mistake if I did at all).D.x
is unknown; the rest we can treat as if they were constants) to getsqr(D.x) + 2*D.x - sqr(B.x) + sqr(AC.m*D.x) + 2*AC.b*D.x - 2*AC.m*D.x*B.y + sqr(AC.b) - 2*AC.b*B.y + sqr(B.y) == 4*r*r
(sqr(D.x) + sqr(AC.m*D.x)) + ( 2*D.x + 2*AC.b*D.x - 2*AC.m*B.y*D.x ) + ( - sqr(B.x) + sqr(AC.b) - 2*AC.b*B.y + sqr(B.y) ) == 4*r*r
which can be refactored to(1 + sqr(AC.m)) * sqr(D.x) + 2*( 1 + AC.b - AC.m*B.y ) * D.x + ( sqr(B.y) - sqr(B.x) + sqr(AC.b) - 2*AC.b*B.y - 4*r*r ) == 0
x == (-bb +- sqrt( sqr(bb) - 4*aa*cc ) / 2*aa
) (usingaa
to avoid confusion with earlier variables), withaa := 1 + sqr(AC.m);
,bb := 2*( 1 + AC.b - AC.m*B.y );
, andcc := sqr(B.y) - sqr(B.x) + sqr(AC.b) - 2*AC.b*B.y - sqr(A.r+B.r);
.-bb/2aa +- sqrt(sqr(bb)-4*aa*cc)/2*aa
:first_term := -bb/(2*a);
andsecond_term := sqrt(sqr(bb)-4*aa*cc)/2*aa;
.D1.x = first_term + second_term;
withD1.y = AC.m * D1.x + AC.b
, and a second solutionD2.x = first_term + second_term;
withD2.y = AC.m * D2.x + AC.b
.A
:D1.l := sqrt( sqr(D1.x-A.x) + sqr(D1.y-A.y) );
andD2.l = sqrt( sqr(D2.x-A.x) + sqr(D2.y-A.y) );
(actually it is more efficient to skip both square roots, but it makes no difference).D := D1 if D1.l < D2. l else D2;
.DB
, lets call it E, is the collision (I don't know how this generalises if the radii aren't equal).DB.m := (B.y-D.y)/(B.x-D.x);
andDB.b = B.y - DB.m*B.x;
.BD.l == A.r + B.r
, sosqrt( sqr(E.x-B.x) + sqr(E.y-B.y) ) == B.r
.E
because we know it's onBD
soE.y == BD.m * E.x + BD.b
, gettingsqrt( sqr(E.x-B.x) + sqr(BD.m * E.x + BD.b-B.y) ) == B.r
.sqr(E.x) - 2*E.x*B.x + sqr(B.x) + sqr(BD.m)*sqr(E.x) + 2*BD.m*E.x*BD.b - 2*BD.m*B.y + sqr(B.y) - 2*BD.b*B.y + sqr(B.y) == sqr(B.r)
sqr(E.x) + sqr(BD.m)*sqr(E.x) + 2*BD.m*E.x*BD.b - 2*E.x*B.x + sqr(B.x) - 2*BD.m*B.y + sqr(B.y) - 2*BD.b*B.y + sqr(B.y) == sqr(B.r)
(1 + sqr(BD.m)) * sqr(E.x) + 2*(BD.m*BD.b - B.x) * E.x + sqr(B.x) + sqr(B.y) - 2*BD.m*B.y + sqr(B.y) - 2*BD.b*B.y + sqr(B.y) - sqr(B.r) == 0
aa := (1 + sqr(BD.m)); bb := 2*(BD.m*BD.b - B.x); cc := sqr(B.y) - 2*BD.m*B.y + sqr(B.y) - 2*BD.b*B.y + sqr(B.y) - sqr(B.r);
, quadratic formula, and then get your two points and choose the one closer to B.I wish I was just whoring for karma or the necromancer badge, but I really needed to work this out and figured I'd share. Ugh, I think I need to lay down now.