如何在3D空间中计算旋转后的碰撞?

3
在我的程序中,我需要计算一个旋转的长方形与一个球以及两个旋转长方形之间的碰撞。目前我找不到相关信息,尝试自己解决这个数学问题非常困难。
我已经实现了两个长方形之间以及球和长方形之间的碰撞检测,但现在需要考虑角度。这是我目前的代码:
class Box
{
public:
    Box();

private:
    float m_CenterX, m_CenterY, m_CenterZ, m_Width, m_Height, m_Depth;
    float m_XRotation, m_YRotation, m_ZRotation;
};

class Sphere
{
public:
   Sphere();

private:
   float m_CenterX, m_CenterY, m_CenterZ, radius;
   unsigned char m_Colour[3];
};

bool BoxBoxCollision(BoxA, BoxB)
{
    //The sides of the Cubes 
    float leftA, leftB; 
    float rightA, rightB;
    float topA, topB; 
    float bottomA, bottomB;
    float nearA, nearB;
    float farA, farB;

    //center pivot is at the center of the object
    leftA = A.GetCenterX() - A.GetWidth(); 
    rightA = A.GetCenterX() + A.GetWidth(); 
    topA = A.GetCenterY() - A.GetHeight(); 
    bottomA = A.GetCenterY() + A.GetHeight();
    farA = A.GetCenterZ() - A.GetDepth(); 
    nearA = A.GetCenterZ() + A.GetDepth();

    leftB = B.GetCenterX() - B.GetWidth(); 
    rightB = B.GetCenterX() + B.GetWidth(); 
    topB = B.GetCenterY() - B.GetHeight(); 
    bottomB = B.GetCenterY() + B.GetHeight();
    farB = B.GetCenterZ() - B.GetDepth(); 
    nearB = B.GetCenterZ() + B.GetDepth();

    //If any of the sides from A are outside of B 
    if( bottomA <= topB ) { return false; }
    if( topA >= bottomB ) { return false; }
    if( rightA <= leftB ) { return false; }
    if( leftA >= rightB ) { return false; }
    if( nearA <= farB ) { return false; }
    if( farA >= nearB ) { return false; }

     //If none of the sides from A are outside B 
    return true;
}

bool SphereBoxCollision( Sphere& sphere, Box& box) 
{ 
    float sphereXDistance = abs(sphere.getCenterX() - box.GetCenterX());
    float sphereYDistance = abs(sphere.getCenterY() - box.GetCenterY());
    float sphereZDistance = abs(sphere.getCenterZ() - box.GetCenterZ());

    if (sphereXDistance >= (box.GetWidth() + sphere.getRadius())) { return false; }
    if (sphereYDistance >= (box.GetHeight() + sphere.getRadius())) { return false; }
    if (sphereZDistance >= (box.GetDepth() + sphere.getRadius())) { return false; }

    if (sphereXDistance < (box.GetWidth())) { return true; } 
    if (sphereYDistance < (box.GetHeight())) { return true; }
    if (sphereZDistance < (box.GetDepth())) { return true; }

   float cornerDistance_sq = ((sphereXDistance - box.GetWidth()) * (sphereXDistance - box.GetWidth())) +
                         ((sphereYDistance - box.GetHeight()) * (sphereYDistance - box.GetHeight()) +
                         ((sphereYDistance - box.GetDepth()) * (sphereYDistance - box.GetDepth())));

    return (cornerDistance_sq < (sphere.getRadius()*sphere.getRadius()));
}

我该如何考虑旋转?有什么建议吗?


这里的概念不是很清晰。你似乎混淆了2D和3D。我建议先将其全部转换为3D。 - Logicrat
在代码中,所有看起来都是3D的。但术语“rect”应该是“box”或“cuboid”。 - Moby Disk
重复:https://dev59.com/X1TTa4cB1Zd3GeqPurCj - Moby Disk
@Logicrat 我理解我的命名方式可能会让人感到困惑。一开始是2D,但后来我改成了3D。我现在会进行修正。 - SyntaxIsEvil
游戏设计的本质在于虚拟表现,以此实现良好的外观和体验,同时保持计算效率。任意旋转的盒子之间的碰撞在计算上非常昂贵,并且没有任何游戏优势可言。相反,它会增加代理人卡在几何图形中的可能性。因此,我强烈建议不要使用这种方式设计。不可移动的世界应该由盒子组成,而所有可移动的代理人应该由球体组成,因此您只需要考虑盒子与球体以及球体与球体之间的碰撞,这非常简单。 - Mike Nakis
显示剩余5条评论
2个回答

1

首先,您的对象是盒子,而不是矩形。矩形一词严格用于二维图形。

当您处理旋转时,通常应将其视为仿射变换的特殊形式。仿射变换可以是旋转、平移、缩放操作、剪切操作或这些操作的任意组合,并且可以由一个简单的4x4矩阵表示,该矩阵乘以给出盒子顶点的向量。也就是说,您可以将任何旋转、缩放、剪切的盒子描述为应用仿射变换的单位盒子(向量<0,0,0>到<1,1,1>之间的盒子)。

大多数仿射变换的矩阵(除了那些缩放因子为零的)可以被反转,这样你就可以把任何点转化成盒子坐标系的形式,并将其与<0,0,0>和<1,1,1>进行比较以检查它是否在盒子内部。同时,你也可以将盒子坐标系中的任意点重新转换回你的世界坐标系(例如,你可以通过变换向量<0.5, 0.5, 0.5>来找到盒子的中心)。由于当仿射变换应用于直线时,直线仍然保持直线,所以你只需要变换盒子的顶点即可。
现在,您可以将一个盒子的顶点(<0,0,0>, <0,0,1>, ...)转换为您的世界坐标系,然后将它们转换回另一个盒子的坐标系。之后,问题是否两个盒子重叠就变成了描述转换后的八个顶点所表示的盒子是否与单位盒子重叠的问题。现在,您可以轻松地决定是否有一个顶点在单位盒子的底面上方(y > 0),在顶部平面下方(y < 1),等等。不幸的是,要覆盖盒子/盒子交集的情况很多,与球体、射线、平面等这样的复杂对象相比,它们更容易相交。但是,将一个盒子固定在单位盒子上应该会有很大帮助。

旁注:

对于3D旋转,了解如何使用四元数是值得的。欧拉角和类似系统都存在万向节锁定问题,而四元数没有这种限制。

基本上,每个单位四元数描述了围绕单个自由轴的旋转。当你将两个单位四元数相乘时,你会得到第三个四元数,它给出了应用两个四元数后得到的旋转结果。而且,由于计算四元数的乘法逆元是微不足道的,所以你也可以将一个四元数除以另一个四元数,以回答一个问题:为了从一个旋转状态到另一个旋转状态,你需要施加哪种单轴旋转。这最后一部分在欧拉角方面是根本不可能做到的。四元数真的是数学中最甜蜜的部分之一。

我无法在这个答案中涵盖所有细节,这个主题非常广泛和有趣。这就是为什么我链接了四篇维基百科文章。如果你需要进一步了解,请阅读它们。


我不明白。你能再详细解释一下吗? - SyntaxIsEvil
@SyntaxIsEvil 我现在已经扩展了我的回答,希望能对你有所帮助。 - cmaster - reinstate monica

0

对于盒子-盒子碰撞,将坐标变换为第一个盒子以原点为中心并与轴对齐。然后检查第二个盒子是否与其碰撞更容易,尽管这不是很简单。对于大多数情况(小dt*v的物理引擎,您可以假设运动是连续的),只需检查任何顶点是否落在第一个盒子内即可。

对于盒子-球体,更简单。像之前一样,将坐标变换为盒子以原点为中心并与轴对齐。现在,您只需要检查盒子中心与每个规范平面(由轴生成)之间的距离是否小于球体半径加上盒子在法向量方向上跨度的一半。


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