Cocos2d-x教程(33)-三维物体AABB碰撞检测算法

出处 :http://blog.csdn.net/u012945598/article/details/39524343

在Cocos2d-x 3.x版本添加了对3D物体的支持后,3D物体的碰撞检测方法也随之更新,其中一种最简单的碰撞检测方法就是AABB碰撞检测。

1. AABB包围盒

        在游戏中,为了简化物体之间的碰撞检测运算,通常会对物体创建一个规则的几何外形将其包围。

        其中,AABB(axis-aligned bounding box)包围盒被称为轴对其包围盒。

        二维场景中的AABB包围盒具备特点:(注:由于Cocos2d-x是基于Opengl ES的,所以下图中的所有坐标系均采用右手直角坐标系)

       (1) 表现形式为四边形,即用四边形包围物体。

       (2) 四边形的每一条边,都会与坐标系的轴垂直。

        如图 1-1 所示:

           图1-1

         三维场景中的AABB包围盒特点:

       (1) 表现形式为六面体。

       (2) 六面体中的每条边都平行于一个坐标平面。


        如图 1-2 所示:

图 1-2(图片来源百度)


在图1-2中,为了更明显的展示AABB包围盒的特点,在最右侧展示了一个OBB(Oriented Bounding Box)包围盒,也称作有向包围盒。

        可以看出,AABB包围盒与OBB包围盒的最直接的区别就是,AABB包围盒是不可以旋转的,而OBB包围盒是可以旋转的,也就是有向的。

2. 二维场景中的AABB碰撞检测原理

        首先来看一张二维场景中的物体碰撞图:

图 2-1

        在图 2-1中,分别做物体A与物体B在X,Y轴方向的投影,物体A的Y轴方向最大点坐标为Y1,最小点坐标Y2,X轴方向最小点坐标X1,最大点坐标X2,物体B同理。

图中红色区域为物体A与物体B投影的重叠部分。

        可以看出,AABB碰撞检测具有如下规则:

        物体A与物体B分别沿两个坐标轴做投影,只有在两个坐标轴都发生重叠的情况下,两个物体才意味着发生了碰撞。


        所以,在程序中做二维游戏的AABB碰撞检测时,只需验证物体A与物体B是否满足如下条件:

        (1)物体A的Y轴方向最小值大于物体B的Y轴方向最大值;

        (2)物体A的X轴方向最小值大于物体B的X轴方向最大值;

        (3)物体B的Y轴方向最小值大于物体A的Y轴方向最大值;

        (4)物体B的X轴方向最小值大于物体A的X轴方向最大值;

        若满足上述条件,则证明物体A与物体B并未发生重合,反之,则证明物体A与物体B重合。

3. 三维场景中的AABB碰撞检测原理

       首先,再来看一下图2-1中的二维物体A和物体B的包围盒,可以发现实际上判断物体A与物体B是否发生重合只需要知道两个信息:

       (1) 物体A的最小点的信息,即图2-1中A的左下角点;以及物体A的最大点的信息,即图2-1中A的右上角点。

       (2) 物体B的最小点的信息,物体B的最大点的信息。

       也就是说在二维场景的碰撞检测中,每个物体的顶点坐标信息都可以由两个坐标来确定,即两个坐标就可以标识一个物体了,所以两个物体的碰撞检测只需要获得到四个点坐标就可以了。

       之前在图1-2中已经看到,三维场景中物体的AABB包围盒是一个六面体,其坐标系对于二维坐标系来讲只是多了一个Z轴,所以实际上在三维场景中物体的AABB碰撞检测依然可以采用四个点信息的判定来实现。即从物体A的八个顶点与物体B的八个顶点分别选出两个最大与最小的顶点进行对比。三维物体的AABB包围盒的八个顶点依旧可以用两个顶点来标识,如图 3-1 所示:


图3-1

        只要确定了图中黑色点部分的坐标,就可以确定八个顶点的全部信息了。

在Cocos2d-x 3.x版本中,为开发者提供了AABB类,用于保存包围盒的最大顶点与最小顶点的信息,并且为每个Sprite3D对象提供了获取AABB包围盒的接口,在AABB类同时提供了判断相应的碰撞检测的方法。有一点需要注意的是,CCAABB类中一开始保存的最大顶点与最小顶点的信息实际上是物体坐标系中的信息,而实际上在碰撞检测时需要将其转换成世界坐标系中的点,这一过程在Sprite3D中的getAABB()方法中实现,可通过CCAABB中的

transform()方法来完成。

        下面对AABB的源码进行分析:

CCAABB.h 文件

class CC_3D_DLL AABB

{

public:

    /**

    * 构造函数

    */

    AABB();


    /**

    * 构造函数 参数:最小顶点坐标,最大顶点坐标

    */

    AABB(const Vec3& min, const Vec3& max);


    /**

    * 构造函数 参数:AABB包围盒

    */

    AABB(const AABB& box);


    /**

    * 获取包围盒中心点坐标

    */

    Vec3 getCenter();


    /* 获取包围盒八个顶点信息

    * Z轴正方向的面

    * verts[0] : 左上顶点

    * verts[1] : 左下顶点

    * verts[2] : 右下顶点

    * verts[3] : 右上顶点

    *

    * Z轴负方向的面

    * verts[4] : 右上顶点

    * verts[5] : 右下顶点

    * verts[6] : 左下顶点

    * verts[7] : 左上顶点

    */

    void getCorners(Vec3 *dst) const;

    /**

    * 判断两个包围盒是否重合

    */

    bool intersects(const AABB& aabb) const;

    /**

    * 判断一个点是否在包围盒内

    */

    bool containPoint(const Vec3& point) const;

    /**

    由两个包围盒生成一个能同时包围这两个包围盒的最小包围盒

    */

    void merge(const AABB& box);

    /**

    * 设置包围盒的最大顶点与最小顶点

    */

    void set(const Vec3& min, const Vec3& max);


    /**

    * 复位函数 初始化最大最小顶点信息

    */

    void reset();


    bool isEmpty() const;

    /**

    * 更新最大顶点与最小顶点信息

    */

    void updateMinMax(const Vec3* point, ssize_t num);


    /**

    * 由一个矩阵对对包围盒进行顶点变换

    */

    void transform(const Mat4& mat);

public:

    Vec3 _min;  //三维向量 保存最小点坐标

    Vec3 _max;  //三维向量 保存最大点坐标

};

NS_CC_END

CCAABB.cpp 文件

#include "3d/CCAABB.h"

NS_CC_BEGIN

//构造函数

AABB::AABB()

{

    reset(); //初始化最大顶点与最小顶点

}

AABB::AABB(const Vec3& min, const Vec3& max)

{

    set(min, max); //设置最大顶点与最小顶点

}

AABB::AABB(const AABB& box)

{

set(box._min,box._max); //设置最大顶点与最小顶点

}

//获取包围盒中心点坐标

Vec3 AABB::getCenter()

{

    Vec3 center;

center.x = 0.5f*(_min.x+_max.x);

center.y = 0.5f*(_min.y+_max.y);

center.z = 0.5f*(_min.z+_max.z);

    return center;

}

//获取包围盒八个顶点信息

void AABB::getCorners(Vec3 *dst) const

{

    assert(dst);


    // 朝着Z轴正方向的面

    // 左上顶点坐标

    dst[0].set(_min.x, _max.y, _max.z);

    // 左下顶点坐标

    dst[1].set(_min.x, _min.y, _max.z);

    // 右下顶点坐标

    dst[2].set(_max.x, _min.y, _max.z);

    // 右上顶点坐标

    dst[3].set(_max.x, _max.y, _max.z);

    // 朝着Z轴负方向的面

    // 右上顶点坐标

    dst[4].set(_max.x, _max.y, _min.z);

    // 右下顶点坐标

    dst[5].set(_max.x, _min.y, _min.z);

    // 左下顶点坐标

    dst[6].set(_min.x, _min.y, _min.z);

    // 左上顶点坐标

    dst[7].set(_min.x, _max.y, _min.z);

}

//判断两个包围盒是否碰撞

bool AABB::intersects(const AABB& aabb) const

{

    return ((_min.x >= aabb._min.x && _min.x <= aabb._max.x) || (aabb._min.x >= _min.x && aabb._min.x <= _max.x)) &&

          ((_min.y >= aabb._min.y && _min.y <= aabb._max.y) || (aabb._min.y >= _min.y && aabb._min.y <= _max.y)) &&

          ((_min.z >= aabb._min.z && _min.z <= aabb._max.z) || (aabb._min.z >= _min.z && aabb._min.z <= _max.z));

}

//判断点和包围盒是否碰撞

bool AABB::containPoint(const Vec3& point) const

{

if (point.x < _min.x) return false;

if (point.y < _min.y) return false;

if (point.z < _min.z) return false;

if (point.x > _max.x) return false;

if (point.y > _max.y) return false;

if (point.z > _max.z) return false;

return true;

}

//生成一个新的包围盒 同时容纳两个包围盒

void AABB::merge(const AABB& box)

{

    // 计算新的最小点坐标

    _min.x = std::min(_min.x, box._min.x);

    _min.y = std::min(_min.y, box._min.y);

    _min.z = std::min(_min.z, box._min.z);

    // 计算新的最大点坐标

    _max.x = std::max(_max.x, box._max.x);

    _max.y = std::max(_max.y, box._max.y);

    _max.z = std::max(_max.z, box._max.z);

}

//设置最大顶点与最小顶点

void AABB::set(const Vec3& min, const Vec3& max)

{

    this->_min = min;

    this->_max = max;

}

//顶点复位 初始化信息

void AABB::reset()

{

_min.set(99999.0f, 99999.0f, 99999.0f);

_max.set(-99999.0f, -99999.0f, -99999.0f);

}

//检测坐标信息是否有误

bool AABB::isEmpty() const

{

    return _min.x > _max.x || _min.y > _max.y || _min.z > _max.z;

}

//由给定点坐标点重新确定最大最小的坐标向量

void AABB::updateMinMax(const Vec3* point, ssize_t num)

{

    for (ssize_t i = 0; i < num; i++)

    {

        // 最小x坐标

        if (point[i].x < _min.x)

            _min.x = point[i].x;


        // 最小y坐标

        if (point[i].y < _min.y)

            _min.y = point[i].y;


        // 最小z坐标

        if (point[i].z < _min.z)

            _min.z = point[i].z;


        // 最大x坐标

        if (point[i].x > _max.x)

            _max.x = point[i].x;


        // 最大y坐标

        if (point[i].y > _max.y)

            _max.y = point[i].y;


        // 最大z坐标

        if (point[i].z > _max.z)

            _max.z = point[i].z;

    }

}

//通过给定的变换矩阵对包围盒进行变换

void AABB::transform(const Mat4& mat)

{

    Vec3 corners[8]; //保存包围盒八个顶点

    //朝向z轴正方向的面

    //左上顶点坐标

    corners[0].set(_min.x, _max.y, _max.z);

    //左下顶点坐标

    corners[1].set(_min.x, _min.y, _max.z);

    //右下顶点坐标

    corners[2].set(_max.x, _min.y, _max.z);

    //右上顶点坐标

    corners[3].set(_max.x, _max.y, _max.z);

    //朝向z轴负方向的面

    //右上顶点坐标

    corners[4].set(_max.x, _max.y, _min.z);

    //右下顶点坐标

    corners[5].set(_max.x, _min.y, _min.z);

    //左下顶点坐标

    corners[6].set(_min.x, _min.y, _min.z);

    //左上顶点坐标

    corners[7].set(_min.x, _max.y, _min.z);


    //顶点变换

    for (int i = 0; i < 8; i++)

        mat.transformPoint(&corners[i]);

    //复位最大顶点最小顶点

    reset();

    //重新计算最大最小点信息

    updateMinMax(corners, 8);

}

NS_CC_END

4. 总结

最后,AABB碰撞检测算法虽然计算方法简单,速度快,但是仅适用于精度不搞的游戏中。相对于AABB碰撞检测,还有一种更逼近物体并更为精确的一种算法——OBB碰撞检测。在Cocos2d-x 中同样提供了OBB碰撞检测的相应方法,如图 4-1所示:


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容

  • 深入理解傅里叶变换Mar 12, 2017 这原本是我在知乎上对傅立叶变换、拉普拉斯变换、Z变换的联系?为什么要进...
    价值趋势技术派阅读 5,685评论 2 2
  • 这是侑虎科技第239篇原创文章,感谢作者冯委供稿,欢迎转发分享,未经作者授权请勿转载。当然,如果您有任何独到的见解...
    玛玛哈哈m阅读 3,802评论 2 3
  • 在顶点着色器处理图元顶点之后进入图元装配阶段。这一阶段,执行裁剪、透视分割和Viewport变换操作。 光栅化是将...
    cain_huang阅读 5,257评论 0 4
  • 以前一直做持续发布使用xcbuild和xctool这些工作进行打包,但是经常遇到证书相关的问题,各种Provisi...
    炸鸡叔阅读 8,037评论 12 53
  • 一 我出生地叫由来村,这个村子里到处是拥有奇妙能力的人,比如说会喷火、会呼风唤雨和可以预见未来的人。我们被称为异人...
    人造月球阅读 791评论 11 19