OBB全称oriented bounding box,比AABB(axis-aligned bounding box)多了一个方向性。
求交核心思想:向量点积的投影意义,unitX为(1,0)单位向量, A.dot( unitX ) 结果值为为A点的x分量,表示意义是A点在x轴上的投影。
1)一维求交
如图1,线段[t1, t2]和线段[t3, t4]相交 当且仅当 两个线段在T轴上是投影区间相交,即
t3 < t4 && t1 < t2 && !(t3 >= t2 || t1 >= t4)2)二维AABB相交 如图2,蓝色OBB在X轴投影区间为[x1, x3],在Y轴投影区间为[y1, y3];橙色矩形在X轴投影为[x2, x4],在Y轴投影区间为[y2, y4]。二维的OBB求交通过投影,转化为两个一维的求交运算。 蓝色、红色矩形相交当且仅当它们在X、Y轴上的投影区间同时相交。即
!(x2 >= x3 || x1 >= x4) && !(y2 >= y3 || y1 >= y4)3)二维OBB求交
如图3,蓝色、橙色为任意朝向包围矩形。我们选橙色矩形相邻两条正交边为投影轴S和T轴,且橙色矩阵在S轴上投影区间为[s1, s2],在T轴上投影区间为[t1, t2];蓝色矩形在S轴投影区间为[s3, s4],在T轴上投影为[t3, t4]。与AABB求交思路类似,OBB橙色矩形和蓝色矩形相交则一定得出他们在坐标系S-T轴上的投影区间分别相交,但不是充分必要件:
左图中,首先将蓝色、橙色矩形分别投影在橙色矩阵确定的S、T轴上,投影区间都相交,但是目测两个矩形其实并没有相交;再次将蓝色、橙色矩形分别投影到在蓝色矩形确定的S、T轴上,结果二者在S轴上的投影区间并不相交,从而得出蓝色、橙色矩形其实并不相交。
因此,OBB相交测试中需要投影到橙色的坐标系上做投影测试,如果通过则投影到蓝色矩形坐标系上做测试,只有两次都相交才可以。代码实际执行时,第一次相交测试已经能过滤至少50%的case,只有剩余的50%再次执行第二次相交测试。
google obb求交,第一个资料就是flipcode的教程:http://www.flipcode.com/archives/2D_OBB_Intersection.shtml
我实际测试发现flip code上的obb代码并不能正确运行。
下面是我修改后的程序: 2dobb.h
class Vector2 { public: typedef float data_type; Vector2() { m_Data[0] = m_Data[1] = 0.0f; } Vector2(const Vector2& other) { m_Data[0] = other.m_Data[0]; m_Data[1] = other.m_Data[1]; } Vector2(data_type x, data_type y) { m_Data[0] = x; m_Data[1] = y; } double dot(const Vector2& other) const { return other.m_Data[0] * m_Data[0] + other.m_Data[1] * m_Data[1]; } double squaredLength() const { return sqrtf(m_Data[0]*m_Data[0] + m_Data[1]*m_Data[1]); } Vector2& operator/(data_type factor) { m_Data[0] /= factor; m_Data[1] /= factor; return *this; } Vector2& operator/=(data_type factor) { m_Data[0] /= factor; m_Data[1] /= factor; return *this; } Vector2& operator*(data_type factor) { m_Data[0] *= factor; m_Data[1] *= factor; return *this; } Vector2& operator*=(data_type factor) { m_Data[0] *= factor; m_Data[1] *= factor; return *this; } Vector2& operator+=(const Vector2& other) { m_Data[0] += other.m_Data[0]; m_Data[1] += other.m_Data[1]; return *this; } Vector2& operator=(const Vector2& other) { if (this==&other) { return *this; } m_Data[0] = other.m_Data[0]; m_Data[1] = other.m_Data[1]; return *this; } operator const data_type* () const { return &(m_Data[0]); } const data_type* data() const { return &(m_Data[0]); } friend static Vector2 operator-(const Vector2& first, const Vector2& second) { data_type x = first.m_Data[0] - second.m_Data[0]; data_type y = first.m_Data[1] - second.m_Data[1]; return Vector2(x, y); } friend static Vector2 operator+(const Vector2& first, const Vector2& second) { data_type x = first.m_Data[0] + second.m_Data[0]; data_type y = first.m_Data[1] + second.m_Data[1]; return Vector2(x, y); } private: data_type m_Data[2]; }; class OBB2D { private: /** Corners of the box, where 0 is the lower left. */ Vector2 corner[4]; /** Two edges of the box extended away from corner[0]. */ Vector2 axis[2]; /** origin[a] = corner[0].dot(axis[a]); */ double minProjLength[2]; // 原点 0点在两个轴上的投影 double maxProjLength[2]; // 2点在两个轴上的投影 /** Returns true if other overlaps one dimension of this. */ bool overlaps1Way(const OBB2D& other) const { for (int a = 0; a < 2; ++a) { double t = other.corner[0].dot(axis[a]); // Find the extent of box 2 on axis a double tMin = t; double tMax = t; for (int c = 1; c < 4; ++c) { t = other.corner[c].dot(axis[a]); if (t < tMin) { tMin = t; } else if (t > tMax) { tMax = t; } } // We have to subtract off the origin // See if [tMin, tMax] intersects [minProjLength, maxProjLength] if (tMin > maxProjLength[a] || tMax < minProjLength[a]) { // There was no intersection along this dimension; // the boxes cannot possibly overlap. return false; } } // There was no dimension along which there is no intersection. // Therefore the boxes overlap. return true; } /** Updates the axes after the corners move. Assumes the corners actually form a rectangle. */ void computeAxes() { axis[0] = corner[1] - corner[0]; axis[1] = corner[3] - corner[0]; // Make the length of each axis 1/edge length so we know any // dot product must be less than 1 to fall within the edge. for (int a = 0; a < 2; ++a) { axis[a] /= axis[a].squaredLength(); minProjLength[a] = corner[0].dot(axis[a]); maxProjLength[a] = corner[2].dot(axis[a]); } } public: OBB2D(const Vector2& center, const double w, const double h, double angle) { Vector2 X( cos(angle), sin(angle)); Vector2 Y(-sin(angle), cos(angle)); X *= w / 2; Y *= h / 2; corner[0] = center - X - Y; corner[1] = center + X - Y; corner[2] = center + X + Y; corner[3] = center - X + Y; computeAxes(); } void updateAngle(const Vector2& center, const double w, const double h, double angle) { Vector2 X( cos(angle), sin(angle)); Vector2 Y(-sin(angle), cos(angle)); X *= w / 2; Y *= h / 2; corner[0] = center - X - Y; corner[1] = center + X - Y; corner[2] = center + X + Y; corner[3] = center - X + Y; computeAxes(); } /** For testing purposes. */ void moveTo(const Vector2& center) { Vector2 centroid = (corner[0] + corner[1] + corner[2] + corner[3]) / 4; Vector2 translation = center - centroid; for (int c = 0; c < 4; ++c) { corner[c] += translation; } computeAxes(); } /** Returns true if the intersection of the boxes is non-empty. */ bool overlaps(const OBB2D& other) const { return overlaps1Way(other) && other.overlaps1Way(*this); } void render() const { glBegin(GL_LINE_LOOP); for (int c = 0; c < 5; ++c) { glVertex2fv(corner[c & 3]); } glEnd(); } };
glut的测试框架:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <assert.h> #include <time.h> #include <math.h> #include <gl/glut.h> #include <iostream> using namespace std; #include "2dobb.h" const int g_window_size = 512; int g_window_width = g_window_size; int g_window_height = g_window_size; const char* g_app_string = "OBB2DDemo"; OBB2D* g_rotateObbPtr; OBB2D* g_moveObbPtr; float g_obbAngle = 10; Vector2 g_moveObbCenter(50, 50); GLenum checkForError(char *loc); void Init(void) { g_rotateObbPtr = new OBB2D( Vector2(100, 100), 50,100, g_obbAngle ); g_moveObbPtr = new OBB2D( g_moveObbCenter, 50,100, 0 ); glEnable(GL_CULL_FACE); } void Render(void) { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClearDepth(0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); if (g_rotateObbPtr->overlaps(*g_moveObbPtr)) { glColor3f(0, 1, 0); } else { glColor3f(1, 0, 0); } g_rotateObbPtr->render(); g_moveObbPtr->render(); glutSwapBuffers(); checkForError("swap"); } void Reshape(int width, int height) { g_window_width = width; g_window_height = height; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0f, (GLfloat) g_window_width, 0.0f, (GLfloat) g_window_height, -1.0f, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glViewport(0, 0, g_window_width, g_window_height); } void keyboard(unsigned char key, int x, int y) { int state = -1; switch (key) { case '1': g_obbAngle += 0.1; g_rotateObbPtr->updateAngle(Vector2(100, 100), 50, 100, g_obbAngle); break; case 'w': // move up state = 0; break; case 's': state = 1; break; case 'a': state = 3; break; case 'd': state = 2; break; case 'q': delete g_rotateObbPtr; delete g_moveObbPtr; exit(0); } if (state >= 0) { Vector2::data_type dx = 10 * (state < 2 ? 0 : (state==3 ? -1 : 1) ); Vector2::data_type dy = 10 * (state < 2 ? (state==0 ? 1 : -1) : 0 ); g_moveObbCenter += Vector2(dx, dy); g_moveObbPtr->moveTo(g_moveObbCenter); } glutPostRedisplay(); } int main(int argc, char *argv[]) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH); glutInitWindowSize(g_window_width, g_window_height); glutCreateWindow(g_app_string); // set up world space to screen mapping glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0f, (GLfloat)g_window_size, 0.0f, (GLfloat)g_window_size, -1.0f, 1.0f); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glViewport(0, 0, g_window_size, g_window_size); glutDisplayFunc(Render); glutReshapeFunc(Reshape); glutKeyboardFunc(keyboard); Init(); glutMainLoop(); return 0; } GLenum checkForError(char *loc) { GLenum errCode; const GLubyte *errString; if ((errCode = glGetError()) != GL_NO_ERROR) { errString = gluErrorString(errCode); printf("OpenGL error: %s",errString); if (loc != NULL) printf("(%s)",loc); printf("\n"); } return errCode; }