二维中的OBB相交测试
from:http://blog.csdn.net/ryfdizuo/article/details/12655855
1. 背景知识
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%再次执行第二次相交测试。
2. 代码
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;
- }