二维中的OBB相交测试

  • Post author:
  • Post category:其他


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;
}




版权声明:本文为dizuo原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。