(译)LearnOpenGL实际案例Breakout:碰撞检测

Posted 键盘春秋

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(译)LearnOpenGL实际案例Breakout:碰撞检测相关的知识,希望对你有一定的参考价值。

英文原文

当尝试检测碰撞是否在两个对象之间发生的时候,我们通常不使用对象自己的数据除非这个对象很复杂;这反过来让碰撞检测变得复杂。出于这个原因,我们通常练习的时候更多的使用简单的形状(常常有很好的数学上的定义)覆盖在原始对象上方来碰撞检测。然后我们基于这些简单的形状来让我们的代码更简单并且省下很多性能。这样的一些例子是圆形,球星,三角形和方形;这些用起来比有上百个三角形的网格简单。
当他们给我们更加简单高效的碰撞检测算法,这些更简单的碰撞形状有一个共同的缺点:这些形状通常不会完全覆盖对象。这个特性意味着碰撞可能在没有真的碰撞到实际对象的时候被检测到;有一点需要常常记住的是这些形状和真实的形状相似。

1. AABB - AABB 碰撞

AABB代表轴线对齐盒子(axis-aligned bounding box),这是一个对齐到场景边界的矩形碰撞形状,这在2D中排列到x和y轴。进行轴线对齐意味着矩形盒子没有旋转并且它的边和场景的基准轴平行(例如:左和右边平行于y轴)。事实是这些盒子一直对齐到场景的轴让计算更加简单。这里我们用AABB检测覆盖这个球对象:

Breakout中几乎所有的对象都是长方形对象,这让场景使用AABB碰撞检测很合适。这就是我们实际上要做的。
AABB能够被几个步骤定义。定义AABB的一步是通过左上角和右下角坐标。已经被我们定义好的GameObject类常常包含一个左上角坐标(它们的位置向量)并且我们能够简单的通过将它的尺寸加到左上坐标向量来计算它的右下坐标(Position+Size)。实际上,包含了AABB的每一个GameObject我们都能够用来碰撞。
因此我们如何定义碰撞检测?当两个碰撞形状进入任何一个其他区域时一个碰撞会发生。例如:形状检测到第一个物体一定程度上被包含在第二个物体中。在AABB中这很容易检测因为他们对其到了场景的轴:我们检查任何一个轴,如果这两个对象的边在轴上重合。基本上我们检查两个对象的水平和垂直边界是否重合。如果水平和垂直边界都重合我们就发生了碰撞。

解释这个代码的意义太过直白。我们检查所有轴线是否覆盖,然后返回碰撞:

GLboolean CheckCollision(GameObject &one, GameObject &two) // AABB - AABB collision

    // Collision x-axis?
    bool collisionX = one.Position.x + one.Size.x >= two.Position.x &&
        two.Position.x + two.Size.x >= one.Position.x;
    // Collision y-axis?
    bool collisionY = one.Position.y + one.Size.y >= two.Position.y &&
        two.Position.y + two.Size.y >= one.Position.y;
    // Collision only if on both axes
    return collisionX && collisionY;

我们检查第一个对象的右边界是否比第二个对象的左边界高并且是否第二个对象的有弊恩杰比第一个对象的左边界高;竖直方向类似处理。如果你看起来有疑问,尝试在纸上画出边界/四边形并且亲自来确定它。
为了让碰撞代码更加有条理,我们添加一个额外的方法到Game类:

class Game

public:
    [...]
    void DoCollisions();
;
在DoCollisions中我们在小球和关卡的砖块之间检查碰撞。如果我们触发了一个碰撞,我们设置砖块的Destroyed属性为true并且立即终止关卡渲染这个砖块。
void Game::DoCollisions()

    for (GameObject &box : this->Levels[this->Level].Bricks)
    
        if (!box.Destroyed)
        
            if (CheckCollision(*Ball, box))
            
                if (!box.IsSolid)
                    box.Destroyed = GL_TRUE;
            
        
    

然后我们还需要更新game的Update方法:

void Game::Update(GLfloat dt)

    // Update objects
    Ball->Move(dt, this->Width);
    // Check for collisions
    this->DoCollisions();

如果我们现在运行代码,小球将会和任何一个砖块触发碰撞并且如果砖块不是固体,这个砖块被销毁。
原文此处有个视频可以查看运行时效果。
当碰撞检测运行时,这不是很精确,因为小球和大部分砖块没有直接接触就碰撞了它们。让我们实现另一个碰撞检测技术。

2. AABB - Circle 碰撞检测

由于小球是圆形对象,AABB可能不是小球的碰撞形状的最佳选择。碰撞代码认为小球是一个四边形盒子所以小球常常在小球自己的精灵没有触碰到砖块的时候碰撞到砖块。

这让场景对小球使用圆形碰撞形状代替AABB会更好。基于这个原因我们包含了一个半径值到小球对象。定义一个圆形碰撞形状我们所需的所有内容是一个位置矢量和一个半径。

这意味着我们需要更新检测算法因为它当前仅仅在两个AABB之间工作。在一个圆形和 一个四边形之间检测稍微有些复杂,但是砖块是这样的:我们在AABB上找到距离圆最近的点并且如果从这个点到圆心的距离小于半径,则发生了碰撞。
最难的是在AABB上找到最近的点P。下图显示了我们如何在任意AABB和圆形之间计算这个点:

我们首先在小球的中心C和AABB的中心B之间获得一个差异矢量D。然后我们需要固定D到AABB的半边长w和h。矩形的半边长是矩形的中心和它的边的距离;基本上它的尺寸分开为两个。这返回一个位于AABB边上的位置矢量(除非圆形的中心在AABB中)。
一个夹逼算法在给定的范围内夹逼一个值到另一个值。这通常像这样表示:

float clamp(float value, float min, float max) 
    return std::max(min, std::min(max, value));

例如,在3.0f和6.0f之间42.0f的夹逼值是6.0f,4.2f的夹逼值是4.2f。
夹逼一个2D矢量意味着我们在给定的范围内夹逼x和y分量。
夹逼矢量p是从AABB到圆最近的点。然后我们需要做的是在圆心C和矢量P之间计算差异矢量D。

现在我们有了矢量D我们能够用它的长度和圆的半径进行比较来检测是否有碰撞。下面是所有的代码:

GLboolean CheckCollision(BallObject &one, GameObject &two) // AABB - Circle collision

    // Get center point circle first 
    glm::vec2 center(one.Position + one.Radius);
    // Calculate AABB info (center, half-extents)
    glm::vec2 aabb_half_extents(two.Size.x / 2, two.Size.y / 2);
    glm::vec2 aabb_center(
        two.Position.x + aabb_half_extents.x,
        two.Position.y + aabb_half_extents.y
        );
    // Get difference vector between both centers
    glm::vec2 difference = center - aabb_center;
    glm::vec2 clamped = glm::clamp(difference, -aabb_half_extents, aabb_half_extents);
    // Add clamped value to AABB_center and we get the value of box closest to circle
    glm::vec2 closest = aabb_center + clamped;
    // Retrieve vector between center circle and closest point AABB and check if length <= radius
    difference = closest - center;
    return glm::length(difference) < one.Radius;

一个CheckCollision的重载方法被创建,特别用于BallObject和GameObject之间。因为我们不会在对象自己之间存储碰撞形状信息,我们需要计算它们:首先小球的中心被计算,然后是AABB的半边长和它的中心。
使用这些碰撞形状特性我们夹逼到AABB的中心获得最近的点P然后计算矢量D。然后我们在中心和最近点P之间计算另一个向量D’并且返回两个形状是否发生了碰撞。
由于我们已经在球对象上调用了CheckCollision方法,我们不需要改变任何代码重载的于CheckCollision的变量就会自动应用。结果是一个更精确的碰撞检测算法。
原文此处有个视频可以查看运行时效果。
转载请注明出处:http://blog.csdn.net/ylbs110/article/details/52760781
它看起来运行得很好,但是仍然有些不对劲。我们正确的做了所有的碰撞检测,但是小球没有在碰撞中发生任何反馈。我们需要反馈碰撞。例如:一旦发生碰撞就更新小球的位置和/或速度。下一节我们将会探讨这个主题。

以上是关于(译)LearnOpenGL实际案例Breakout:碰撞检测的主要内容,如果未能解决你的问题,请参考以下文章

(译)LearnOpenGL实际案例Breakout:音频

(译)LearnOpenGL实际案例Breakout:渲染文本

(译)LearnOpenGL实际案例Breakout:渲染文本

(译)LearnOpenGL实际案例Breakout:小球

(译)LearnOpenGL实际案例Breakout:碰撞反馈

(译)LearnOpenGL实际案例Breakout:碰撞检测