如何检测2D矩形物体碰撞的方向?


11

之后这个问题,我需要一些更多的帮助。

如何找出碰撞发生在矩形的哪一边并做出相应的反应?

矩形从四面八方碰撞

蓝色箭头是一些圆形对象在与盒子碰撞之前和之后所遵循的路径。

我该如何计算?

Answers:


8

由于这是基于您的其他问题,因此我将为矩形何时轴向对齐提供解决方案。

首先,使用以下值构建当前对象的矩形:

int boxLeft = box.X;
int boxRight = boxLeft + box.Width;
int boxTop = box.Y;
int boxBottom = boxTop + box.Height;

接下来,您必须具有旧对象的位置(可以将其存储在每个对象上或直接传递给函数)以创建旧对象的矩形(当它不发生碰撞时):

int oldBoxLeft = box.OldX;
int oldBoxRight = oldBoxLeft + box.Width;
int oldBoxTop = box.OldY;
int oldBoxBottom = oldBoxTop + box.Height;

现在,要知道碰撞来自何处,您必须找到旧位置不在碰撞区域以及新位置在哪一侧。因为,当您想到它时,就会发生碰撞:没有碰撞的一侧进入另一个矩形。

这是您可以执行的操作(这些函数假定存在冲突。如果没有冲突,则不应调用它们):

 bool collidedFromLeft(Object otherObj)
{
    return oldBoxRight < otherObj.Left && // was not colliding
           boxRight >= otherObj.Left;
}

再次重复。

bool collidedFromRight(Object otherObj)
{
    return oldBoxLeft >= otherObj.Right && // was not colliding
           boxLeft < otherObj.Right;
}

bool collidedFromTop(Object otherObj)
{
    return oldBoxBottom < otherObj.Top && // was not colliding
           boxBottom >= otherObj.Top;
}

bool collidedFromBottom(Object otherObj)
{
    return oldBoxTop >= otherObj.Bottom && // was not colliding
           boxTop < otherObj.Bottom;
}

现在,对于实际使用中来自另一个问题的碰撞响应:

if (collidedFromTop(otherObj) || collidedFromBottom(otherObj))
    obj.Velocity.Y = -obj.Velocity.Y;
if (collidedFromLeft(otherObj) || collidedFromRight(otherObj))
    obj.Velocity.X = -obj.Velocity.X;

再次,这可能不是最佳解决方案,但这就是我通常用于冲突检测的方式。


再一次,你是对的!; D谢谢...(下次再给我寄来您董事会的更多明信片... ^ ___ ^)
NemoStein 2011年

遗憾的是,我不知道我可以用它来做什么..也许下次!
杰西·埃蒙德

7

由于问题与该问题部分相同,因此我将重用部分答案,以便尝试回答您的问题。


让我们定义一个上下文和一些变量,以使下面的解释更容易理解。我们将在此处使用的表示形式可能不适合您自己的数据形式,但这种方式应该更易于理解(实际上,一旦您理解了原理,就可以使用以下方法使用其他类型的表示形式)

因此,我们将考虑轴对齐边界框(或定向边界)和移动实体

  • 边界框由4个边组成,我们将每个边定义为:
    Side1 = [x1,y1,x2,y2](两个点[x1,y1]和[x2,y2])

  • 将移动实体定义为速度矢量(位置+速度):
    位置[posX,posY]和速度[speedX,speedY]


您可以使用以下方法确定矢量击中AABB / OBB的哪一侧:

  • 1 /找到通过AABB的四个边的无限线与通过实体位置(预碰撞)的无限线之间的交点,这些实体使用实体速度矢量作为斜率。(您可以找到一个碰撞点或一个不确定的数字,该数字对应于平行线或重叠线)

  • 2 /一旦知道了交点(如果存在),就可以搜索线段边界内的交点。

  • 3 /最后,如果列表上仍然有多个点(速度矢量可以通过多个边),则可以使用从相交到实体原点的向量的幅度来搜索与实体原点最近的点。

然后,您可以使用简单的点积确定碰撞角度。

  • 4 /使用实体(可能是球?)向量的点积与击中边向量找出碰撞之间的夹角。

----------

更多细节:

  • 1 /找到路口

    • a /使用其参数形式(P(t)= Po + tD)确定无穷线(Ax + Bx = D)。

      点原点:Po = [posX,posY]
      方向向量:D = [speedX,speedY]

      A = Dy = speedY
      B = -Dx = -speedX
      D =(Po.x * Dy)-(Po.y * Dx)= (posX speedY)-(posY speedX)

      Ax + By = D <====>(speedY x)+(-speedX y)=(posX speedY)-(posY speedX)

      我使用实体点值来说明该方法,但这与确定边界框的4条边无限线的方法完全相同(使用Po = [x1,y1]和D = [x2-x1; y2-y1]代替)。

    • b /接下来,要找到两条无限线的交点,我们可以求解以下系统:

      A1x + B1x = D1 <==以速度矢量为斜率通过实体点的线。
      A2x + B2x = D2 <==穿过AABB边的线之一。

      产生以下拦截坐标:

      拦截x =((B 2 * D 1)-(B 1 * D 2))/((A 1 * B 2)-(A 2 * B 1))
      拦截y =((A 1 * D 2)- (A 2 * D 1))/((A 1 * B 2)-(A 2 * B 1))

      如果分母((A1 * B2)-(A2 * B1))等于零,则两条线是平行或重叠的,否则应找到一个交点。

  • 2 /测试线段边界。由于这很容易验证,因此不需要更多详细信息。

  • 3 /搜索最近的点。如果列表上还有多个点,我们可以找到最靠近实体原点的那一侧。

    • a /确定从交点到实体原点的向量

      V = Po-Int = [Po.x-Int.x; Po.y-Int.y]

    • b /计算向量幅度

      || V || = sqrt(V.x²+V.y²)

    • c /找到最小的一个。
  • 4 /现在您知道要打哪一侧,您可以使用点积确定角度。

    • a /令S = [x2-x1; y2-y1]是将要命中的辅助向量,并且E = [speedX; speedY]为实体速度向量。

      使用向量点积规则,我们知道

      S·E = Sx Ex + Sy Ey

      S·E = || S || || E || cosθ

      所以我们可以通过操纵这个方程来确定θ

      cosθ=(S·E)/(|| S || || E ||)

      θ= acos((S·E)/(|| S || || E ||)))

      S·E = Sx * Ex + Sy * Ey
      || S || = sqrt(Sx²+Sy²)
      || E || = sqrt(Ex²+Ey²)


注意:正如我在另一个问题线程中所说的那样,这可能不是最高效也不是最简单的方法,这只是我们要想到的,部分数学可能会有所帮助。

我没有使用具体的OBB示例进行验证(我使用AABB进行了验证),但它也应该适用。


6

一种简单的方法是解决碰撞,然后在每个方向上依次将运动对象的碰撞盒平移一个像素,然后查看哪个像素导致碰撞。

如果要“正确”地使用旋转的碰撞形状或任意多边形,建议阅读“分离轴定理”。例如,Metanet软件(开发N游戏的人)在SAT上一篇很棒的开发文章。他们还讨论了所涉及的物理学。


2

一种方法是围绕矩形旋转世界。在这种情况下,“世界”只是您关心的对象:矩形和球。围绕矩形的中心旋转矩形,直到其边界与x / y轴对齐,然后将球旋转相同的量。

这里的重点是,您绕矩形的中心而不是围绕矩形的中心旋转球。

然后,您可以像测试其他非旋转矩形一样轻松地测试碰撞。


另一个选择是将矩形视为四个不同的线段,并分别测试它们之间的碰撞。这使您可以测试碰撞找出同时发生碰撞的那一侧。


1

我在计算中使用了固定角度,但这应该对您有所帮助

void Bullet::Ricochet(C_Rect *r)
{
    C_Line Line;
    //the next two lines are because I detected 
    // a collision in my main loop so I need to take a step back.

    x = x + ceil(speed * ((double)fcos(itofix(angle)) / 65536));
    y = y + ceil(speed * ((double)fsin(itofix(angle)) / 65536));
    C_Point Prev(x,y);

    //the following checks our position to all the lines will give us
    // an answer which line we will hit due to no lines
    // with angles > 90 lines of a rect always shield the other lines.

    Line = r->Get_Closest_Line(Prev);    
    int langle = 0;
    if(!Line.Is_Horizontal())   //we need to rotate the line to a horizontal position
    {
        langle = Line.Get_Point1().Find_Fixed_Angle(Line.Get_Point2());
        angle = angle - langle;  //to give us the new angle of approach
    }
    //at this point the line is horizontal and the bullet is ready to be fixed.
    angle = 256 - angle;
    angle += langle;
}
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.