在OpenGL中实现边界球碰撞

时间:2014-04-05 14:49:03

标签: c++ opengl collision-detection

我理解边界球碰撞的基本原理,但是实现它会让我感到困惑。

如果我在数组中定义了两个立方体: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];

    }
}

1 个答案:

答案 0 :(得分:2)

形状的中心点可以通过多种方式计算,具体取决于您想要考虑的“中心”。但是,对于立方体,中心计算通常被认为是其点的平均值,这相对简单:只需通过将所有向量相加并除以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} }倍边长。

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

您可以选择最适合您需求的产品。第一个具有最佳的“角落”检测,但是他最差的“面部”检测,第二个具有最佳的“面部”检测和最差的“角落”检测,而第三个具有或多或少的第一个“平均”二,如果所有情况都是可能的话,给予它最“可靠”的准确性。