笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:《手把手教你架构3D游戏引擎》电子工业出版社和《Unity3D实战核心技术详解》电子工业出版社等。
AABB(Axis-Aligned Bounding Box)包围盒被称为轴对其包围盒。其在坐标系中的表示如下图:
在Cocos2d-x 3.x版本中,为开发者提供了AABB类,用于保存包围盒的最大顶点与最小顶点的信息,并且为每个Sprite3D对象提供了获取AABB包围盒的接口,在AABB类同时提供了判断碰撞检测的方法。有一点需要注意的是,AABB类中一开始保存的最大顶点与最小顶点的信息实际上是物体坐标系中的信息,在碰撞检测时需要将其转换成世界坐标系中的点,可通过AABB类中的transform()方法来完成。下面对AABB的源码进行分析:
#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
{
//就是各轴互相是否包含,(aabb 包含当前包围盒)|| (当前的包围盒包含 aabb)
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;
returntrue;
}
//生成一个新的包围盒同时容纳两个包围盒,新的包围盒的_min 各轴要是其他两个最小的那个,_max各轴要是其他两个最大的那个
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++)
{
// 左边点.
if (point[i].x < _min.x)
_min.x = point[i].x;
//最低点
if (point[i].y < _min.y)
_min.y = point[i].y;
// 最远点.
if (point[i].z < _min.z)
_min.z = point[i].z;
// 右边最大点
if (point[i].x > _max.x)
_max.x = point[i].x;
// 最高点
if (point[i].y > _max.y)
_max.y = point[i].y;
// 最近点
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]); //mat 是变换矩阵,变换&corners[i] 向量。
//复位最大顶点最小顶点
reset();
//重新计算最大最小点信息
updateMinMax(corners, 8);
}
当然在AABB类中也使用了矩阵转换函数,因为场景中的物体是可以移动旋转的,下面把AABB碰撞盒的变换函数给大家展示一下:
void AABB::transform(const Mat4& mat)
{
Vec3corners[8];
// 左-顶-前.
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);
// 右-顶-背面.
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);
}
物体在游戏场景中并不是静止不动的,而是可以移动、旋转的,它自身的碰撞盒也要根据物体的变化而变换。在函数的最后是碰撞盒顶点的更新函数如下所示:
void AABB::updateMinMax(const Vec3*point, ssize_tnum)
{
for (ssize_t i = 0; i < num; i++)
{
// 左边最大点.
if(point[i].x<_min.x)
_min.x = point[i].x;
// 最低的点.
if(point[i].y<_min.y)
_min.y = point[i].y;
// 最远的点.
if(point[i].z<_min.z)
_min.z = point[i].z;
// 右边最大点.
if(point[i].x>_max.x)
_max.x = point[i].x;
// 最高的点.
if (point[i].y>_max.y)
_max.y = point[i].y;
// 最近的点.
if(point[i].z>_max.z)
_max.z = point[i].z;
}
}
总结:
AABB碰撞盒其实就是一个立方体,它在程序中计算效率比较高,但是在碰撞检测方面做得比较粗糙,比如一把水壶,它用AABB立方体包围后,会有很多空隙,检测效果并不是很理想,但是运算性能非常好。