如何在平移/旋转后重新计算轴对齐的边界框

How to recalculate axis-aligned bounding box after translate/rotate

本文关键字:计算 对齐 边界 新计算 旋转      更新时间:2023-10-16

当我第一次加载对象时,我用最大和最小(x,y,z)点计算初始AABB。但这是在物体空间中,物体在世界各地移动,更重要的是,旋转。

如何在每次平移/旋转对象时重新计算新的 AABB?这基本上发生在每一帧中。每帧重新计算新的 AABB 将是一个非常密集的操作吗?如果是这样,还有什么选择?

我知道 AABB 会使我的碰撞检测不那么准确,但实现碰撞检测代码比 OBB 更容易,我想一步一步来。

这是我从以下答案中获得一些见解后当前的代码:

typedef struct sAxisAlignedBoundingBox {
    Vector3D bounds[8];
    Vector3D max, min;
} AxisAlignedBoundingBox;
void drawAxisAlignedBoundingBox(AxisAlignedBoundingBox box) {
    glPushAttrib(GL_LIGHTING_BIT | GL_POLYGON_BIT);
    glEnable(GL_COLOR_MATERIAL);
    glDisable(GL_LIGHTING);
    glColor3f(1.0f, 1.0f, 0.0f);
    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[0].x, box.bounds[0].y, box.bounds[0].z);
        glVertex3f(box.bounds[1].x, box.bounds[1].y, box.bounds[1].z);
        glVertex3f(box.bounds[2].x, box.bounds[2].y, box.bounds[2].z);
        glVertex3f(box.bounds[3].x, box.bounds[3].y, box.bounds[3].z);
    glEnd();
    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[4].x, box.bounds[4].y, box.bounds[4].z);
        glVertex3f(box.bounds[5].x, box.bounds[5].y, box.bounds[5].z);
        glVertex3f(box.bounds[6].x, box.bounds[6].y, box.bounds[6].z);
        glVertex3f(box.bounds[7].x, box.bounds[7].y, box.bounds[7].z);
    glEnd();
    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[0].x, box.bounds[0].y, box.bounds[0].z);
        glVertex3f(box.bounds[5].x, box.bounds[5].y, box.bounds[5].z);
        glVertex3f(box.bounds[6].x, box.bounds[6].y, box.bounds[6].z);
        glVertex3f(box.bounds[1].x, box.bounds[1].y, box.bounds[1].z);
    glEnd();
    glBegin(GL_LINE_LOOP);
        glVertex3f(box.bounds[4].x, box.bounds[4].y, box.bounds[4].z);
        glVertex3f(box.bounds[7].x, box.bounds[7].y, box.bounds[7].z);
        glVertex3f(box.bounds[2].x, box.bounds[2].y, box.bounds[2].z);
        glVertex3f(box.bounds[3].x, box.bounds[3].y, box.bounds[3].z);
    glEnd();
    glPopAttrib();
}
void calculateAxisAlignedBoundingBox(GLMmodel *model, float matrix[16]) {
    AxisAlignedBoundingBox box;
    float dimensions[3];
    // This will give me the absolute dimensions of the object
    glmDimensions(model, dimensions);
    // This calculates the max and min points in object space
    box.max.x = dimensions[0] / 2.0f, box.min.x = -1.0f * box.max.x;
    box.max.y = dimensions[1] / 2.0f, box.min.y = -1.0f * box.max.y;
    box.max.z = dimensions[2] / 2.0f, box.min.z = -1.0f * box.max.z;
    // These calculations are probably the culprit but I don't know what I'm doing wrong
    box.max.x = matrix[0] * box.max.x + matrix[4] * box.max.y + matrix[8] * box.max.z + matrix[12];
    box.max.y = matrix[1] * box.max.x + matrix[5] * box.max.y + matrix[9] * box.max.z + matrix[13];
    box.max.z = matrix[2] * box.max.x + matrix[6] * box.max.y + matrix[10] * box.max.z + matrix[14];
    box.min.x = matrix[0] * box.min.x + matrix[4] * box.min.y + matrix[8] * box.min.z + matrix[12];
    box.min.y = matrix[1] * box.min.x + matrix[5] * box.min.y + matrix[9] * box.min.z + matrix[13];
    box.min.z = matrix[2] * box.min.x + matrix[6] * box.min.y + matrix[10] * box.min.z + matrix[14];
    /* NOTE: If I remove the above calculations and do something like this:
             box.max = box.max + objPlayer.position;
             box.min = box.min + objPlayer.position;
             The bounding box will move correctly when I move the player, the same does not
             happen with the calculations above. It makes sense and it's very simple to move
             the box like this. The only problem is when I rotate the player, the box should
             be adapted and increased/decreased in size to properly fit the object as a AABB.
    */
    box.bounds[0] = Vector3D(box.max.x, box.max.y, box.min.z);
    box.bounds[1] = Vector3D(box.min.x, box.max.y, box.min.z);
    box.bounds[2] = Vector3D(box.min.x, box.min.y, box.min.z);
    box.bounds[3] = Vector3D(box.max.x, box.min.y, box.min.z);
    box.bounds[4] = Vector3D(box.max.x, box.min.y, box.max.z);
    box.bounds[5] = Vector3D(box.max.x, box.max.y, box.max.z);
    box.bounds[6] = Vector3D(box.min.x, box.max.y, box.max.z);
    box.bounds[7] = Vector3D(box.min.x, box.min.y, box.max.z);
    // This draw call is for testing porpuses only
    drawAxisAlignedBoundingBox(box);
}
void drawObjectPlayer(void) {
    static float mvMatrix[16];
    if(SceneCamera.GetActiveCameraMode() == CAMERA_MODE_THIRD_PERSON) {
        objPlayer.position = SceneCamera.GetPlayerPosition();
        objPlayer.rotation = SceneCamera.GetRotationAngles();
        objPlayer.position.y += -PLAYER_EYE_HEIGHT + 0.875f;
        /* Only one of the two code blocks below should be active at the same time
           Neither of them is working as expected. The bounding box doesn't is all
           messed up with either code. */
        // Attempt #1
        glPushMatrix();
            glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
            glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
            glCallList(gameDisplayLists.player);
            glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
        glPopMatrix();
        // Attempt #2
        glPushMatrix();
            glLoadIdentity();
            glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
            glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
            glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
        glPopMatrix();
        calculateAxisAlignedBoundingBox(objPlayer.model, mvMatrix);
    }
}

但它不能正常工作...我做错了什么?

只需重新计算转换后的 AABB 的 AABB。这意味着转换 8 个顶点(8 个顶点 - 矩阵乘法)和 8 个顶点-顶点比较。

因此,在初始化时,您在模型空间中计算 AABB:对于模型每个顶点的每个 x,y,z,您检查 xmin、xmax、ymin、ymax 等。

对于每一帧,您都会生成一个新的转换矩阵。在OpenGL中,这是通过glLoadIdentity完成的,然后是glTransform/Rotate/Scale(如果使用旧的API)。正如lmmilewski所说,这就是模型矩阵。

您可以再次计算此转换矩阵(在 OpenGL 之外,例如使用 glm)。你也可以使用glGet获取OpenGL的结果矩阵。

您将 AABB 的八个顶点中的每一个都乘以此矩阵。使用 glm 进行矩阵向量乘法。你会得到你变身后的AABB(在世界空间中)。它很可能旋转了(不再轴对齐)。

现在你的算法可能只适用于轴对齐的东西,因此你的问题。因此,现在您通过对转换后的边界框进行标记来近似转换模型的新边界框:

对于新 AABB 的每个顶点的每个 x,y,z,您检查 xmin、xmax、ymin、ymax 等。这为您提供了一个可以在剪辑算法中使用的世界空间 AABB。

这不是最佳的(AABB明智)。你会得到很多空白空间,但从性能上讲,重新计算整个网格的AABB要好得多。

<小时 />

至于转换矩阵,在drawObjectPlayer中:

gLLoadIdentity();
glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
glGetFloatv(GL_MODELVIEW_MATRIX, mvMatrix);
// Now you've got your OWN Model Matrix (don't trust the GL_MODELVIEW_MATRIX flag : this is a workaround, and I know what I'm doing ^^ )
gLLoadIdentity(); // Reset the matrix so that you won't make the transformations twice
gluLookAt( whatever you wrote here earlier )
glTranslatef(objPlayer.position.x, objPlayer.position.y, objPlayer.position.z);
glRotatef(objPlayer.rotation.y + 180.0f, 0.0f, 1.0f, 0.0f);
// Now OpenGL is happy, he's got his MODELVIEW matrix correct ( gluLookAt is the VIEW part; Translate/Rotate is the MODEL part
glCallList(gameDisplayLists.player); // Transformed correcty

我无法进一步解释...正如评论中所说,您必须做两次。顺便说一句,在OpenGL 3中,您不会遇到这些问题和丑陋的解决方法,因为您将完全负责自己的矩阵。在 OpenGL 2 中等效:

glm::mat4 ViewMatrix = glm::LookAt(...);
glm::mat4 ModelMatrix = glm::rotate() * glm::translate(...);
// Use ModelMatrix for whatever you want
glm::mat4 ModelViewMatrix = ViewMatrix * ModelMatrix;
glLoadMatrix4fv( &ModelViewMatrix[0][0] ); // In OpenGL 3 you would use an uniform instead

干净多了,对吧?

是的,您可以转换八个角顶点并对结果执行最小值/最大值,但有一种更快的方法,正如 Jim Arvo 在 Graphics Gems (1990) 一章中所描述的那样。

在性能方面,Arvo的方法大致相当于两个转换而不是八个,基本上如下(这将框A转换为框B

// Split the transform into a translation vector (T) and a 3x3 rotation (M).
B = zero-volume AABB at T
for each element (i,j) of M:
   a = M[i][j] * A.min[j]
   b = M[i][j] * A.max[j]
   B.min[i] += a < b ? a : b
   B.max[i] += a < b ? b : a
return B

Arvo方法的一种变体使用中心/范围表示而不是混合/最大,克里斯特·埃里克森(Christer Ericson)在实时碰撞检测(照片)中描述了这一点。

完整的 C 代码图形 Gems 文章可以在这里找到。

为此,

您必须遍历每个顶点,计算其在世界中的位置(乘以模型视图)并找到每个对象中的最小/最大顶点坐标(就像第一次计算它一样)。

你可以稍微缩放一下你的 AABB,这样你就不必重新计算它 - 按因子 sqrt(2) 放大它就足够了 - 然后你的旋转对象总是适合 AABB。

还有一个问题,你朝哪个方向旋转(?如果总是在一个,那么你只能在那个方向上放大AABB。

或者,您可以使用边界球体而不是 AABB。那么你不关心旋转,缩放不是问题。

引用

之前在AABB @ Stack Overflow上的回应:

"可悲的是,如果你的角色旋转,你需要重新计算你的AABB

斯库尔梅德尔

<小时 />

受访者的建议,也是我的建议,是一旦你让AABB工作,就实现定向边界框,并且还要注意,你可以制作网格部分的aabb来捏造碰撞检测,比每个对象一个巨大的盒子更准确。

为什么不使用你的 GPU?今天,我通过重新生成几帧来暗示这个问题的解决方案。

  1. 暂时将相机放在物体上方,指向物体上方在对象上。
  2. 仅渲染对象,不渲染灯光或什么。
  3. 也使用正射投影。
  4. 然后读取帧缓冲区。黑色像素的行和列表示模型不存在。击中一个白色像素 - 你击中了模型 AABB 边框之一。

我知道这不是适用于所有情况的解决方案,但通过一些先验知识,这是非常有效的。

有关屏幕外渲染的信息,请参阅此处。