在Stack Overflow社区的帮助下,我写了一个非常基本但有趣的物理模拟器.
单击并拖动鼠标以启动球.它会反弹并最终停在"地板"上.
我想要添加的下一个重要特征是球与球的碰撞.球的运动被分解为ax和y速度矢量.我有重力(每一步的y矢量小减少),我有摩擦(每次与墙碰撞的两个矢量的小减少).球真诚地以令人惊讶的逼真方式移动.
我想我的问题有两个部分:
检测球与球碰撞的最佳方法是什么?
我是否只有一个O(n ^ 2)循环遍历每个球并检查每个其他球以查看它的半径是否重叠?
我用什么方程来处理球与球的碰撞?物理101
它如何影响两个球的速度x/y向量?两个球进入的最终方向是什么?我如何将其应用于每个球?
处理"墙壁"的碰撞检测和由此产生的矢量变化很容易,但我发现球 - 球碰撞的并发症更多.对于墙壁,我只需要采取适当的x或y向量的负数,然后关闭它将朝正确的方向前进.有球我认为不是这样.
一些快速澄清:为了简单起见,我现在可以完全弹性碰撞,现在我的所有球都具有相同的质量,但我将来可能会改变它.
编辑:我发现有用的资源
带矢量的2d球物理:无三角碰撞的二维碰撞.pdf
2d球碰撞检测示例:添加碰撞检测
我的球碰撞检测和响应工作很棒!
相关代码:
碰撞检测:
for (int i = 0; i < ballCount; i++) { for (int j = i + 1; j < ballCount; j++) { if (balls[i].colliding(balls[j])) { balls[i].resolveCollision(balls[j]); } } }
这将检查每个球之间的碰撞但跳过多余的检查(如果你必须检查球1是否与球2碰撞,那么你不需要检查球2是否与球1碰撞.此外,它会跳过检查是否与自身发生碰撞).
然后,在我的球类中,我有我的colliding()和resolveCollision()方法:
public boolean colliding(Ball ball) { float xd = position.getX() - ball.position.getX(); float yd = position.getY() - ball.position.getY(); float sumRadius = getRadius() + ball.getRadius(); float sqrRadius = sumRadius * sumRadius; float distSqr = (xd * xd) + (yd * yd); if (distSqr <= sqrRadius) { return true; } return false; } public void resolveCollision(Ball ball) { // get the mtd Vector2d delta = (position.subtract(ball.position)); float d = delta.getLength(); // minimum translation distance to push balls apart after intersecting Vector2d mtd = delta.multiply(((getRadius() + ball.getRadius())-d)/d); // resolve intersection -- // inverse mass quantities float im1 = 1 / getMass(); float im2 = 1 / ball.getMass(); // push-pull them apart based off their mass position = position.add(mtd.multiply(im1 / (im1 + im2))); ball.position = ball.position.subtract(mtd.multiply(im2 / (im1 + im2))); // impact speed Vector2d v = (this.velocity.subtract(ball.velocity)); float vn = v.dot(mtd.normalize()); // sphere intersecting but moving away from each other already if (vn > 0.0f) return; // collision impulse float i = (-(1.0f + Constants.restitution) * vn) / (im1 + im2); Vector2d impulse = mtd.normalize().multiply(i); // change in momentum this.velocity = this.velocity.add(impulse.multiply(im1)); ball.velocity = ball.velocity.subtract(impulse.multiply(im2)); }
源代码:球与球对撞机的完整来源.
如果有人对如何改进这个基本物理模拟器有一些建议,请告诉我!我还有一点要补充的是角动量,所以球会更逼真地滚动.还有其他建议吗?发表评论!
要检测两个球是否发生碰撞,只需检查它们的中心之间的距离是否小于半径的两倍.要在球之间进行完美的弹性碰撞,您只需要担心碰撞方向上的速度分量.对于两个球,另一个组件(与碰撞相切)将保持不变.您可以通过创建指向从一个球到另一个球的方向的单位矢量来获得碰撞分量,然后使用球的速度矢量获取点积.然后,您可以将这些组件插入一维完全弹性碰撞方程.
维基百科对整个过程进行了很好的总结.对于任何质量的球,可以使用方程计算新的速度(其中v1和v2是碰撞后的速度,u1,u2来自之前):
如果球具有相同的质量,则简单地切换速度.这里是我编写的一些类似的代码:
void Simulation::collide(Storage::Iterator a, Storage::Iterator b) { // Check whether there actually was a collision if (a == b) return; Vector collision = a.position() - b.position(); double distance = collision.length(); if (distance == 0.0) { // hack to avoid div by zero collision = Vector(1.0, 0.0); distance = 1.0; } if (distance > 1.0) return; // Get the components of the velocity vectors which are parallel to the collision. // The perpendicular component remains the same for both fish collision = collision / distance; double aci = a.velocity().dot(collision); double bci = b.velocity().dot(collision); // Solve for the new velocities using the 1-dimensional elastic collision equations. // Turns out it's really simple when the masses are the same. double acf = bci; double bcf = aci; // Replace the collision velocity components with the new ones a.velocity() += (acf - aci) * collision; b.velocity() += (bcf - bci) * collision; }
至于效率,Ryan Fox是对的,您应该考虑将区域分成几个部分,然后在每个部分内进行碰撞检测.请记住,球可能会与部分边界上的其他球发生碰撞,因此这可能会使您的代码更加复杂.效率可能无关紧要,直到你有几百个球.对于奖励积分,您可以在不同核心上运行每个部分,或者拆分每个部分内的碰撞处理.
好吧,多年前我制作了像你这样的节目.
有一个隐藏的问题(或许多,取决于观点):
如果球的速度太高,你可能会错过碰撞.
而且,几乎在100%的情况下,你的新速度将是错误的.好吧,不是速度,而是位置.您必须在正确的位置精确计算新的速度.否则你只需在一些小的"误差"量上移动球,这可以从前面的离散步骤中获得.
解决方案是显而易见的:你必须拆分时间步,这样,首先你转移到正确的位置,然后碰撞,然后转移你剩下的时间.
您应该使用空间分区来解决此问题.
阅读 Binary Space Partitioning 和 Quadtrees
作为对Ryan Fox建议将屏幕划分为区域,并仅检查区域内的碰撞的说明......
例如,将游戏区域划分为正方形网格(将任意地说每边有1个单位长度),并检查每个网格方格内的碰撞.
这绝对是正确的解决方案.它的唯一问题(正如另一张海报所指出的)是跨越边界的碰撞是一个问题.
对此的解决方案是将第二栅格以0.5单位垂直和水平偏移覆盖到第一栅格.
然后,任何跨越第一个网格中的边界(因此未被检测到)的碰撞将在第二个网格中的网格方格内.只要您跟踪已经处理过的碰撞(因为可能存在一些重叠),您就不必担心处理边缘情况.所有碰撞都将位于其中一个网格的网格方框内.
减少碰撞检查次数的一种好方法是将屏幕分成不同的部分.然后,您只将每个球与同一部分中的球进行比较.
我在这里看到的一件事就是优化.
虽然我确实认为当距离是它们的半径之和时球会击中,但实际上不应该计算这个距离!相反,计算它的正方形并以这种方式使用它.这种昂贵的平方根操作没有理由.
此外,一旦发现碰撞,您必须继续评估碰撞,直到不再存在.问题是,在获得准确的图片之前,第一个可能会导致其他人必须解决.想想如果球在边缘击球会发生什么?第二球击中边缘并立即反弹到第一球.如果你撞到角落里的一堆球,你可能会有很多碰撞需要解决,然后再迭代下一个循环.
至于O(n ^ 2),你所能做的只是最大限度地减少拒绝错过的成本:
1)一个没有移动的球不能击中任何东西.如果在地板上有合理数量的球,这可以节省大量的测试.(请注意,您仍然必须检查是否有东西击中固定球.)
2)可能值得做的事情:将屏幕划分为多个区域但线条应该模糊 - 区域边缘的球被列为所有相关(可能是4个)区域.我会使用4x4网格,将区域存储为位.如果两个球区的区域的AND返回零,则测试结束.
3)正如我所提到的,不要做平方根.
我找到了一个很好的页面,其中包含2D中碰撞检测和响应的信息.
http://www.metanetsoftware.com/technique.html
他们试图从学术角度解释它是如何完成的.它们从简单的对象到对象碰撞检测开始,然后继续进行碰撞响应以及如何扩展它.
编辑:更新链接