在OpenGL中实现边界球碰撞

Posted

技术标签:

【中文标题】在OpenGL中实现边界球碰撞【英文标题】:Implementing bounding sphere collision in OpenGL 【发布时间】:2014-04-05 14:49:03 【问题描述】:

我了解边界球碰撞的基本原理,但是实现它让我有点困惑。

如果我在数组中定义了两个立方体:cube1[] 和 cube2[],每个数组由构成每个三角形的 GLfloats 组成。那么我怎样才能首先计算每个立方体的中心点呢?以及如何获得围绕它的球体半径?

计算这个需要什么数学?

编辑:对我的问题进行更多说明。假设我有一个使用以下数组定义的立方体:

GLfloat cube[] = 
     2.0f,  3.0f, -4.0f, // triangle 1, top right
     3.0f,  3.0f, -4.0f,
     2.0f,  2.0f, -4.0f, // bottom right

     3.0f,  3.0f, -4.0f, // triangle 2, back face top left
     3.0f,  2.0f, -4.0f, // bottom left
     2.0f,  2.0f, -4.0f,

     2.0f,  3.0f, -3.0f, // triangle 1, front face top left
     2.0f,  2.0f, -3.0f, // bottom left
     3.0f,  3.0f, -3.0f, // Bottom right

     3.0f,  3.0f, -3.0f, // triangle 2, front face
     2.0f,  2.0f, -3.0f,
     3.0f,  2.0f, -3.0f, // Bottom right

     2.0f,  3.0f, -3.0f, // triangle 1, top face
     3.0f,  3.0f, -3.0f,
     2.0f,  3.0f, -4.0f,
     3.0f,  3.0f, -4.0f, // triangle 2, top face
     2.0f,  3.0f, -4.0f,
     3.0f,  3.0f, -3.0f,

     2.0f,  2.0f, -3.0f, // triangle 1, bottom face
     2.0f,  2.0f, -4.0f,
     3.0f,  2.0f, -3.0f,
     3.0f,  2.0f, -4.0f, // triangle 2, bottom face
     3.0f,  2.0f, -3.0f, // Bottom Right.
     2.0f,  2.0f, -4.0f,

     2.0f,  2.0f, -4.0f, // triangle 1, left face
     2.0f,  2.0f, -3.0f,
     2.0f,  3.0f, -4.0f,
     2.0f,  3.0f, -4.0f, // triangle 2, left face
     2.0f,  2.0f, -3.0f,
     2.0f,  3.0f, -3.0f,

     3.0f,  2.0f, -4.0f, // triangle 1, right face
     3.0f,  3.0f, -4.0f,
     3.0f,  2.0f, -3.0f,
     3.0f,  3.0f, -4.0f, // triangle 2, right face
     3.0f,  3.0f, -3.0f,
     3.0f,  2.0f, -3.0f,

;

鉴于这个立方体,我需要在每次立方体平移时获取中心点并跟踪它。我相信我已经这样做了,但也感谢您提供有关这是否正确的帮助:

// Calculate initial center of the shape
 glm::vec3 corner1 = glm::vec3(2.0f,  3.0f, -4.0f);
 glm::vec3 corner2 = glm::vec3(2.0f,  2.0f, -4.0f);
 glm::vec3 corner3 = glm::vec3(3.0f,  3.0f, -4.0f);
 glm::vec3 corner4 = glm::vec3(3.0f,  2.0f, -4.0f);
 glm::vec3 corner5 = glm::vec3(2.0f,  3.0f, -3.0f);
 glm::vec3 corner6 = glm::vec3(2.0f,  2.0f, -3.0f);
 glm::vec3 corner7 = glm::vec3(3.0f,  3.0f, -3.0f);
 glm::vec3 corner8 = glm::vec3(3.0f,  2.0f, -3.0f);

GLfloat x = (corner1.x + corner2.x + corner3.x + corner4.x + corner5.x + corner6.x+ corner7.x + corner8.x)/8;
GLfloat y = (corner1.y + corner2.y + corner3.y + corner4.y + corner5.y + corner6.y+ corner7.y + corner8.y)/8;
GLfloat z = (corner1.z + corner2.z + corner3.z + corner4.z + corner5.z + corner6.z+ corner7.z + corner8.z)/8;
center = glm::vec4(x, y, z, 1.0f);

使用以下功能检查翻译:

void Cube::Translate(double x, double y, double z)

// Translation matrix for cube.
glm::mat4 cubeTransMatrix = glm::mat4();
cubeTransMatrix = glm::translate(cubeTransMatrix, glm::vec3(x, y, z));
//center = cubeTransMatrix * center;
//Move the cube
for(int i = 0; i < sizeof(cube) / sizeof(GLfloat); i+=3)
        glm::vec4 vector = glm::vec4(cube[i], cube[i+1], cube[i+2], 1.0f);
        glm::vec4 translate = cubeTransMatrix*vector;
        glm::vec4 translateCenter = cubeTransMatrix*center;
        center.x = translateCenter[0];
        center.y = translateCenter[1];
        center.z = translateCenter[2];
        cube[i] = translate[0];
        cube[i+1] = translate[1];
        cube[i+2] = translate[2];

    

【问题讨论】:

对于立方体等凸形,中心只是角顶点的平均值。同样,半径是最长对角线长度的一半(即 (maxx, maxy, maxz) - (minx, miny, minz))。 【参考方案1】:

可以通过多种方式计算形状的中心点,具体取决于您要考虑的“中心”。但是对于一个立方体来说,中心计算一般认为是它的点的平均值,比较简单:把所有的向量加起来除以8就可以得到所有角坐标的平均值。你的立方体的网格,你可能有比这更多的顶点,但对于一个简单的立方体,这不应该是这种情况。

如果您无法访问顶点本身(您加载了一个网格,或者正在使用默认立方体、内置到 GLUT 或其他东西),您将需要跟踪该立方体的转换。我可能会建议为每个立方体使用“局部”位置向量或局部变换矩阵。

在 OpenGL 中,矩阵应该是主要列,所以最右边一列中的前 3 个值应该是您在世界坐标中的位置,在发生任何全局变换之后。

检测碰撞几乎更容易(一旦你完成了整个“预测碰撞何时发生”部分,如果我是你,我不会担心你的第一次实现)。球体是简单的形状,检测两个球体是否相交更加简单。您需要做的就是找到两个球体对撞机之间的平方距离,并将其与它们的平方半径进行比较。

如果两个平方半径之和大于两个球体之间的距离,则它们相交。否则,他们不会。

为了说明这个计算实际上是多么简单,我将在这里向您展示:

float r0sqr = sphere0.radius * sphere0.radius;
float r1sqr = sphere1.radius * sphere1.radius;

float distX = sphere0.position.x - sphere1.position.x;
float distY = sphere0.position.y - sphere1.position.y;
float distZ = sphere0.position.z - sphere1.position.z;

// Since we already need to square these, we won't need to take the absolute value
// to accurately compare them to the radii
distX *= distX;
distY *= distY;
distZ *= distZ;

float sqrDist = (distX+distY+distZ)

if((r0sqr + r1sqr) > sqrDist)

    // They intersect

else

    // They do not intersect

一旦您检测到碰撞,假设您希望球体成为刚体对撞机,将它们彼此远离是非常简单的。只需取两个球体的相交距离。不过为了效率,我们应该稍微修改一下之前的代码:

// Since we already need to square these, we won't need to take the absolute value
// to accurately compare them to the radii
float distSqrX = distX * distX;
float distSqrY = distY * distY;
float distSqrZ = distZ * distZ;

float sqrDist = (distSqrX+distSqrY+distSqrZ);

完成此操作后,我们就可以计算此碰撞的其余分辨率。我们将以一种非常简单的方式进行计算(假设两个物体都没有质量,并且没有影响计算)。

float totalRadius = sphere0.radius + sphere1.radius;// the sum of the two spheres' radii
float dist = sqrt(sqrDist);// the actual distance between the two shapes' centers         
float minMovement = (totalRadius - dist);// the difference between the total radius and the actual distance tells us how far they intersect.

minMovement /= dist;// Divide by the distance to "scale" this movement so we can "scale" our distance vector (distX, distY, and distZ)

float mvmtX = distX * minMovement * 0.5f;// The minimum movement on the x-axis to resolve the collision
float mvmtY = distY * minMovement * 0.5f;// The minimum movement on the y-axis to resolve the collision
float mvmtZ = distZ * minMovement * 0.5f;// The minimum movement on the z-axis to resolve the collision

// For the sake of simplicity, we'll just have them "pop" out of each other, and won't
// be doing any interpolation to "smooth" the spheres' interaction.
//
// However, to ensure that we move the correct collider in the correct direction, we 
// need to see which one is on which side of the other, along the three axes.
if(sphere0.position.x < sphere1.position.x)

    sphere0.position.x -= mvmtX;
    sphere1.position.x += mvmtX;

else

    sphere0.position.x += mvmtX;
    sphere1.position.x -= mvmtX;


// Repeat this process for the other two axes
if(sphere0.position.y < sphere1.position.y)

    sphere0.position.y -= mvmtY;
    sphere1.position.y += mvmtY;

else

    sphere0.position.y += mvmtY;
    sphere1.position.y -= mvmtY;


if(sphere0.position.z < sphere1.position.z)

    sphere0.position.z -= mvmtZ;
    sphere1.position.z += mvmtZ;

else

    sphere0.position.z += mvmtZ;
    sphere1.position.z -= mvmtZ;

最后,计算球体的适当半径以获得对立方体碰撞检测所需的效果可以通过以下三种方式之一完成:

使用外接球体(球体“接触”立方体的角),半径公式为sqrt(3)*edgeLength*0.5。您将获得一个“反应过度”的碰撞检测系统,因为它能够检测到距离立方体体积相当远的碰撞,因为半径能够延伸到盒子的角落。最大的误差点将位于立方体的一个面的中心,球体将在该处过度延伸立方体1/sqrt(3) 乘以立方体的边长。

第二种方法是使用内切球体,其中球体与立方体的面相切(球体“接触”每个立方体面的中心)并且半径为计算为edgeLength*0.5。再一次,会有错误,但这个实际上往往会有更多的错误,因为它会在 8 点“反应不足”,而不是像上一个那样在 6 点“反应过度”。每个角将“反应不足”的距离量(立方体的角与球体表面上最近点之间的距离)与前一个角的过度反应的距离相同,大致为1/sqrt(3) 次边长。

最后一种方法,也是最准确的方法是计算球体,使其与立方体的边缘相切。这个半径的公式是edgeLength/sqrt(2)。这个球体将“接触”立方体每个边缘的中心,并且会“高估”每个面,“低估”每个角落。然而,高估/低估的距离要小得多,而且通常更可以容忍,在任何时候,只有(sqrt(3)*sqrt(2))/2 乘以距离“应该”位置更远的边长(给出大约 1.4 倍的准确结果,对于碰撞)。

您可以选择最适合您的需求。第一个有最好的“角”检测,但他最差的“人脸”检测,第二个有最好的“人脸”检测和最差的“角”检测,第三个或多或少是第一个的“平均值”第二,如果所有情况都有可能,则给予它最“可靠”的准确性。

【讨论】:

您能否建议使用上述方法获取半径的最佳方法?特别是如果我通过使用角落得到中心,我假设它只是从中心到角落的距离就是半径? 这完全由您决定。如果您想要任何级别的准确度,使用球体来检测与立方体的碰撞是相对不可取的,因为您要么必须制作一个与立方体面相切的球体(“小”半径),这将有很大的间隙这不会对立方体角落周围的碰撞做出反应,使其对碰撞“反应不足”,或者您需要制作一个与立方体角落相切的球体,这将对碰撞“过度反应”。我会将这两种方法的计算添加到答案中。 另外,我忘记了使球体与立方体边缘相切的方法,这实际上是这种情况的最佳选择。我也将其添加到答案中。享受吧! 没有。我的意思是边缘的长度。多边形上的“边”是边所属面的 2D 投影上的“边”。用外行的话来说:在连接形成面的两个顶点之间画一条线,这条线就是一条边。立方体被定义为它的所有边的长度相等,因此实际上只需要一个数字来表示立方体上的所有边长。 我已在问题中添加代码以帮助解决问题。我不完全确定我的计算是否正确,因为碰撞似乎不起作用>_

以上是关于在OpenGL中实现边界球碰撞的主要内容,如果未能解决你的问题,请参考以下文章

opengl中的有界框碰撞检测

在OpenGL中实现VBO,窗口保持黑色

在 OpenGL 中实现固定坐标系

在 Qt 中实现的 OpenGL 中级教程

CSharpGL(26)在opengl中实现控件布局/渲染文字

如何在OpenGL中实现灰度渲染?