引言
在前面的一篇文章中讲述了如何通过模型的顶点来求的模型的包围球,并且还讲述了基本包围体除了包围球之外,还有AABB包围盒。在这一章,将讲述如何根据模型的坐标求得它的AABB盒。
表示方法
AABB盒的表示方法有很多,总结起来有如下的三种情况:
Max-min表示法:使用一个右上角和左下角的点来唯一的定义一个包围体
Center-radious表示法:我们用center点来表示中点,radious是一个数组,保存了包围盒在x方向,y方向,z方向上的半径。
Min-Width表示方法:我们用min来定义左下角的点,使用width来保存在x,y,z方向上的长度。
不同的方法,他们的碰撞检测算法也会有所不同,并且不同的表示方法也会适用在不同的情形下。所以,大家自己设计的时候,需要慎重考虑。
在本文中,将会使用的是Max-min表示方法,如下所示:
class AABB { .... public: VECTOR3 max ; VECTOR3 min ; };
在这种表示方法之下,进行碰撞检测的代码如下所示:
bool AABB::isCollided(AABB* a) { if(max.x < a->min.x || min.x > a->max.x) return false ; if(max.y < a->min.y || min.y > a->max.y) return false ; if(max.z < a->min.z || min.z > a->min.z) return false ; return true ; }// end for isCollided
AABB盒构造
构造AABB盒的方法有很多种,有的很简单,有的很复杂,这里将介绍两种基本的构造方法,他们也非常的简单,容易掌握。
第一种是固定大小的AABB盒,这种AABB盒在构造完毕之后,不管被包围的物体怎么样的旋转,都不需要在进行重新构造了。
第二种是比较紧凑的一种,利用X,Y和Z轴向上最长和最远的点来构造一个AABB盒。
固定大小AABB盒
正如上面说的那样,固定大小的AABB盒,它需要被包围的物体,不管怎么旋转,都还在这个包围体里面。所以,我们先为这个物体构造一个包围球体,然后在这个包围球体的基础上构建一个AABB盒。这样就能够达到不管怎么旋转,都还在包围体里面。
不过,为这个物体构建一个包围球同样也能够满足这样的要求,所以,就有点鸡肋了。但是,在某些限制条件下,你无法使用包围球,那么就可以使用这样的方法来构建一个固定大小的AABB盒。
这个算法的核心是如何构建一个包围球体,而这个算法我在前面一章中已经讲述了,就不再重复,感兴趣的读者可以去看博客中3D空间包围球(Bounding Sphere)的求法的文章。
在有了包围球之后,我们通过如下的方法就能够计算出固定大小的AABB盒了:
void AABB::computeFixedAABB(Sphere *s) { max.x = s->center.x + s->radious ; max.y = s->center.y + s->radious ; max.z = s->center.z + s->radious ; min.x = s->center.x - s->radious ; min.y = s->center.y - s->radious ; min.z = s->center.z - s->radious ; }// end for computeFixedAABB
紧凑点的AABB盒
这个AABB盒的构造方法,是从顶点集中获取X,Y和Z方向上最远的和最近的点,然后利用他们来构建一个AABB盒。这种方法也很简单。我直接上代码来给大家讲解:
void AABB::computeAABBFromOriginalPointSet(VECTOR3* vertices, unsigned int vertex_num) { unsigned int minX_i = 0 , maxX_i = 0 ; extrameDistanceAlongDir(MAKE_VECTOR3(1,0,0), vertices, vertex_num, &minX_i, &maxX_i); min.x = vertices[minX_i].x ; max.x = vertices[maxX_i].x ; unsigned int minY_i = 0 , maxY_i = 0; extrameDistanceAlongDir(MAKE_VECTOR3(0,1,0),vertices, vertex_num, &minY_i, &maxY_i); min.y = vertices[minY_i].y ; max.y = vertices[maxY_i].y ; unsigned int minZ_i = 0 , maxZ_i = 0; extrameDistanceAlongDir(MAKE_VECTOR3(0,0,1),vertices, vertex_num, &minZ_i, &maxZ_i); min.z = vertices[minZ_i].z ; max.z = vertices[maxZ_i].z ; }// end for computeAABBFromOriginalPointSet void AABB::extrameDistanceAlongDir(VECTOR3 dir, VECTOR3* vertices, unsigned int vertex_num, unsigned int* min, unsigned int*max) { float maxProj = FLT_MIN , minProj = FLT_MAX ; for(unsigned int i = 0 ; i < vertex_num ; i ++) { float proj = 0 ; Vec3Dot(proj, vertices[i], dir); if(proj > maxProj) { maxProj = proj ; *max = i ; } if(proj < minProj) { minProj = proj ; *min = i ; } }// end for }// end for extrameDistanceAlongDir
上面一共有两个函数,第一个函数就是给用户调用的计算AABB盒的方法。用户只需要将模型的顶点集传递进来即可。
第二个函数,是获取在指定的轴向上,哪个点在这个轴向上的投影是最长的和最短的。这个函数很简单,只需要调用一个点积Dot运算就能够求出。
当求出了在X,Y和Z轴向上投影最长和最短的6个点之后,我们就分别取他们对应的轴向上的坐标值来构成Max和min,这样一个AABB盒就构造完毕了。是不是很简单??
AABB类
下面是AABB的完整类:
//-------------------------------------------------------------------------------------------------- // declaration : Copyright (c), by XJ , 2014 . All right reserved . // brief : This file will define the Axie aligned bounding box. // author : XJ // date : 2014 / 6 / 22 // file : AABB.h // version : 1.0 //--------------------------------------------------------------------------------------------------- #pragma once #include"XJMath.h" #include"Sphere.h" namespace XJCollision { /** * brief : We use the Max-min representation */ class AABB { public: AABB(); AABB(VECTOR3 min, VECTOR3 max); public: /** * Compute the fixed AABB */ void computeFixedAABB(Sphere* s); /** * Compute the AABB from the original point set */ void computeAABBFromOriginalPointSet(VECTOR3 *vertices, unsigned int vertex_num); /** * Collision Detection between two AABB */ bool isCollided(AABB* a); private: /** * Compute the least and most distance along the specific direction */ void extrameDistanceAlongDir(VECTOR3 dir, VECTOR3 * vertices, unsigned int vertex_num, unsigned int * min, unsigned int * max); public: VECTOR3 max ; VECTOR3 min ; }; };
#include"AABB.h" #include<cmath> #include<float.h> using namespace XJCollision ; AABB::AABB() :max(), min() { } AABB::AABB(VECTOR3 _max, VECTOR3 _min) { max.x = _max.x ; max.y = _max.y ; max.z = _max.z ; min.x = _min.x ; min.y = _min.y ; min.z = _min.z ; } void AABB::computeFixedAABB(Sphere *s) { max.x = s->center.x + s->radious ; max.y = s->center.y + s->radious ; max.z = s->center.z + s->radious ; min.x = s->center.x - s->radious ; min.y = s->center.y - s->radious ; min.z = s->center.z - s->radious ; }// end for computeFixedAABB void AABB::computeAABBFromOriginalPointSet(VECTOR3* vertices, unsigned int vertex_num) { unsigned int minX_i = 0 , maxX_i = 0 ; extrameDistanceAlongDir(MAKE_VECTOR3(1,0,0), vertices, vertex_num, &minX_i, &maxX_i); min.x = vertices[minX_i].x ; max.x = vertices[maxX_i].x ; unsigned int minY_i = 0 , maxY_i = 0; extrameDistanceAlongDir(MAKE_VECTOR3(0,1,0),vertices, vertex_num, &minY_i, &maxY_i); min.y = vertices[minY_i].y ; max.y = vertices[maxY_i].y ; unsigned int minZ_i = 0 , maxZ_i = 0; extrameDistanceAlongDir(MAKE_VECTOR3(0,0,1),vertices, vertex_num, &minZ_i, &maxZ_i); min.z = vertices[minZ_i].z ; max.z = vertices[maxZ_i].z ; }// end for computeAABBFromOriginalPointSet void AABB::extrameDistanceAlongDir(VECTOR3 dir, VECTOR3* vertices, unsigned int vertex_num, unsigned int* min, unsigned int*max) { float maxProj = FLT_MIN , minProj = FLT_MAX ; for(unsigned int i = 0 ; i < vertex_num ; i ++) { float proj = 0 ; Vec3Dot(proj, vertices[i], dir); if(proj > maxProj) { maxProj = proj ; *max = i ; } if(proj < minProj) { minProj = proj ; *min = i ; } }// end for }// end for extrameDistanceAlongDir bool AABB::isCollided(AABB* a) { if(max.x < a->min.x || min.x > a->max.x) return false ; if(max.y < a->min.y || min.y > a->max.y) return false ; if(max.z < a->min.z || min.z > a->min.z) return false ; return true ; }// end for isCollided
程序实例
下面的两种图,分别是使用了第一种和第二种计算方法计算出来的包围盒:
3D空间中的AABB(轴向平行包围盒, Aixe align bounding box)的求法