二维中的OBB相交测试

二维中的OBB相交测试

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轴上是投影区间相交,即 

  1. 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轴上的投影区间同时相交。

  1. !(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

  1. class Vector2  
  2. {  
  3. public:   
  4.     typedef float data_type;  
  5.       
  6.     Vector2() {  
  7.         m_Data[0] = m_Data[1] = 0.0f;  
  8.     }  
  9.   
  10.     Vector2(const Vector2& other) {  
  11.         m_Data[0] = other.m_Data[0];  
  12.         m_Data[1] = other.m_Data[1];  
  13.     }  
  14.   
  15.     Vector2(data_type x, data_type y) {  
  16.         m_Data[0] = x;  
  17.         m_Data[1] = y;  
  18.     }  
  19.   
  20.     double dot(const Vector2& other) const {  
  21.         return other.m_Data[0] * m_Data[0] + other.m_Data[1] * m_Data[1];  
  22.     }  
  23.   
  24.     double squaredLength() const {  
  25.         return sqrtf(m_Data[0]*m_Data[0] + m_Data[1]*m_Data[1]);  
  26.     }  
  27.   
  28.     Vector2& operator/(data_type factor) {  
  29.         m_Data[0] /= factor;  
  30.         m_Data[1] /= factor;  
  31.         return *this;  
  32.     }  
  33.   
  34.     Vector2& operator/=(data_type factor) {  
  35.         m_Data[0] /= factor;  
  36.         m_Data[1] /= factor;  
  37.         return *this;  
  38.     }  
  39.   
  40.     Vector2& operator*(data_type factor) {  
  41.         m_Data[0] *= factor;  
  42.         m_Data[1] *= factor;  
  43.         return *this;  
  44.     }  
  45.   
  46.     Vector2& operator*=(data_type factor) {  
  47.         m_Data[0] *= factor;  
  48.         m_Data[1] *= factor;  
  49.         return *this;  
  50.     }  
  51.   
  52.     Vector2& operator+=(const Vector2& other) {  
  53.         m_Data[0] += other.m_Data[0];  
  54.         m_Data[1] += other.m_Data[1];  
  55.         return *this;  
  56.     }  
  57.   
  58.     Vector2& operator=(const Vector2& other) {  
  59.         if (this==&other) {  
  60.             return *this;  
  61.         }  
  62.         m_Data[0] = other.m_Data[0];  
  63.         m_Data[1] = other.m_Data[1];  
  64.         return *this;  
  65.     }  
  66.       
  67.     operator const data_type* () const {  
  68.         return &(m_Data[0]);  
  69.     }  
  70.   
  71.     const data_type* data() const {  
  72.         return &(m_Data[0]);  
  73.     }  
  74.   
  75.     friend static Vector2 operator-(const Vector2& first, const Vector2& second) {  
  76.         data_type x = first.m_Data[0] - second.m_Data[0];  
  77.         data_type y = first.m_Data[1] - second.m_Data[1];  
  78.         return Vector2(x, y);  
  79.     }  
  80.   
  81.     friend static Vector2 operator+(const Vector2& first, const Vector2& second) {  
  82.         data_type x = first.m_Data[0] + second.m_Data[0];  
  83.         data_type y = first.m_Data[1] + second.m_Data[1];  
  84.         return Vector2(x, y);  
  85.     }  
  86.   
  87. private:  
  88.     data_type m_Data[2];  
  89.   
  90. };  
  91.   
  92. class OBB2D {  
  93. private:  
  94.     /** Corners of the box, where 0 is the lower left. */  
  95.     Vector2         corner[4];  
  96.   
  97.     /** Two edges of the box extended away from corner[0]. */  
  98.     Vector2         axis[2];  
  99.   
  100.     /** origin[a] = corner[0].dot(axis[a]); */  
  101.     double          minProjLength[2];       // 原点 0点在两个轴上的投影  
  102.     double          maxProjLength[2];       // 2点在两个轴上的投影  
  103.   
  104.     /** Returns true if other overlaps one dimension of this. */  
  105.     bool overlaps1Way(const OBB2D& other) const {  
  106.         for (int a = 0; a < 2; ++a) {  
  107.   
  108.             double t = other.corner[0].dot(axis[a]);  
  109.   
  110.             // Find the extent of box 2 on axis a  
  111.             double tMin = t;  
  112.             double tMax = t;  
  113.   
  114.             for (int c = 1; c < 4; ++c) {  
  115.                 t = other.corner[c].dot(axis[a]);  
  116.   
  117.                 if (t < tMin) {  
  118.                     tMin = t;  
  119.                 } else if (t > tMax) {  
  120.                     tMax = t;  
  121.                 }  
  122.             }  
  123.   
  124.             // We have to subtract off the origin  
  125.   
  126.             // See if [tMin, tMax] intersects [minProjLength, maxProjLength]  
  127.             if (tMin > maxProjLength[a] || tMax < minProjLength[a]) {  
  128.                 // There was no intersection along this dimension;  
  129.                 // the boxes cannot possibly overlap.  
  130.                 return false;  
  131.             }  
  132.         }  
  133.   
  134.         // There was no dimension along which there is no intersection.  
  135.         // Therefore the boxes overlap.  
  136.         return true;  
  137.     }  
  138.   
  139.   
  140.     /** Updates the axes after the corners move.  Assumes the 
  141.         corners actually form a rectangle. */  
  142.     void computeAxes() {  
  143.         axis[0] = corner[1] - corner[0];   
  144.         axis[1] = corner[3] - corner[0];   
  145.   
  146.         // Make the length of each axis 1/edge length so we know any  
  147.         // dot product must be less than 1 to fall within the edge.  
  148.   
  149.         for (int a = 0; a < 2; ++a) {  
  150.             axis[a] /= axis[a].squaredLength();  
  151.               
  152.             minProjLength[a] = corner[0].dot(axis[a]);  
  153.             maxProjLength[a] = corner[2].dot(axis[a]);  
  154.         }  
  155.     }  
  156.   
  157. public:  
  158.   
  159.     OBB2D(const Vector2& center, const double w, const double h, double angle)  
  160.     {  
  161.         Vector2 X( cos(angle), sin(angle));  
  162.         Vector2 Y(-sin(angle), cos(angle));  
  163.   
  164.         X *= w / 2;  
  165.         Y *= h / 2;  
  166.   
  167.         corner[0] = center - X - Y;  
  168.         corner[1] = center + X - Y;  
  169.         corner[2] = center + X + Y;  
  170.         corner[3] = center - X + Y;  
  171.   
  172.         computeAxes();  
  173.     }  
  174.   
  175.     void updateAngle(const Vector2& center, const double w, const double h, double angle) {  
  176.         Vector2 X( cos(angle), sin(angle));  
  177.         Vector2 Y(-sin(angle), cos(angle));  
  178.   
  179.         X *= w / 2;  
  180.         Y *= h / 2;  
  181.   
  182.         corner[0] = center - X - Y;  
  183.         corner[1] = center + X - Y;  
  184.         corner[2] = center + X + Y;  
  185.         corner[3] = center - X + Y;  
  186.   
  187.         computeAxes();  
  188.     }  
  189.   
  190.     /** For testing purposes. */  
  191.     void moveTo(const Vector2& center) {  
  192.         Vector2 centroid = (corner[0] + corner[1] + corner[2] + corner[3]) / 4;  
  193.   
  194.         Vector2 translation = center - centroid;  
  195.   
  196.         for (int c = 0; c < 4; ++c) {  
  197.             corner[c] += translation;  
  198.         }  
  199.   
  200.         computeAxes();  
  201.     }  
  202.   
  203.     /** Returns true if the intersection of the boxes is non-empty. */  
  204.     bool overlaps(const OBB2D& other) const {  
  205.         return overlaps1Way(other) && other.overlaps1Way(*this);  
  206.     }  
  207.   
  208.     void render() const {  
  209.         glBegin(GL_LINE_LOOP);  
  210.             for (int c = 0; c < 5; ++c) {  
  211.               glVertex2fv(corner[c & 3]);  
  212.             }  
  213.         glEnd();  
  214.     }  
  215. };  

相交时候用绿色绘制线框, 不相交时候用红色绘制线框。

     

glut的测试框架:

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. #include <stdlib.h>  
  4. #include <assert.h>  
  5. #include <time.h>  
  6. #include <math.h>  
  7. #include <gl/glut.h>  
  8. #include <iostream>  
  9. using namespace std;  
  10.   
  11. #include "2dobb.h"  
  12.   
  13. const int g_window_size = 512;  
  14. int g_window_width = g_window_size;  
  15. int g_window_height = g_window_size;  
  16. const char* g_app_string = "OBB2DDemo";  
  17.   
  18. OBB2D*      g_rotateObbPtr;  
  19. OBB2D*      g_moveObbPtr;  
  20. float       g_obbAngle = 10;  
  21. Vector2     g_moveObbCenter(50, 50);  
  22.   
  23. GLenum checkForError(char *loc);  
  24.   
  25. void Init(void)   
  26. {     
  27.     g_rotateObbPtr = new OBB2D( Vector2(100, 100), 50,100, g_obbAngle );  
  28.     g_moveObbPtr = new OBB2D( g_moveObbCenter, 50,100, 0 );  
  29.     glEnable(GL_CULL_FACE);  
  30. }  
  31.   
  32. void Render(void)  
  33. {  
  34.     glClearColor(0.0f, 0.0f, 0.0f, 0.0f);  
  35.     glClearDepth(0.0f);  
  36.     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  
  37.   
  38.     if (g_rotateObbPtr->overlaps(*g_moveObbPtr))   
  39.     {  
  40.         glColor3f(0, 1, 0);  
  41.     } else   
  42.     {  
  43.         glColor3f(1, 0, 0);  
  44.     }  
  45.     g_rotateObbPtr->render();  
  46.     g_moveObbPtr->render();  
  47.   
  48.     glutSwapBuffers();  
  49.     checkForError("swap");  
  50. }  
  51.   
  52. void Reshape(int width, int height)  
  53. {  
  54.     g_window_width = width; g_window_height = height;  
  55.     glMatrixMode(GL_PROJECTION);  
  56.     glLoadIdentity();  
  57.     glOrtho(0.0f, (GLfloat) g_window_width, 0.0f,   
  58.         (GLfloat) g_window_height, -1.0f, 1.0f);  
  59.     glMatrixMode(GL_MODELVIEW);  
  60.     glLoadIdentity();  
  61.     glViewport(0, 0, g_window_width, g_window_height);  
  62. }  
  63.   
  64. void keyboard(unsigned char key, int x, int y) {  
  65.     int state = -1;  
  66.   
  67.     switch (key)  
  68.     {  
  69.     case '1':  
  70.         g_obbAngle += 0.1;  
  71.         g_rotateObbPtr->updateAngle(Vector2(100, 100), 50, 100, g_obbAngle);  
  72.         break;  
  73.     case 'w':   // move up  
  74.         state = 0;  
  75.         break;  
  76.     case 's':  
  77.         state = 1;  
  78.         break;  
  79.     case 'a':  
  80.         state = 3;  
  81.         break;  
  82.     case 'd':  
  83.         state = 2;  
  84.         break;  
  85.     case 'q':  
  86.         delete g_rotateObbPtr;  
  87.         delete g_moveObbPtr;  
  88.         exit(0);  
  89.     }  
  90.   
  91.     if (state >= 0) {  
  92.         Vector2::data_type dx = 10 * (state < 2 ? 0 : (state==3 ? -1 : 1) );  
  93.         Vector2::data_type dy = 10 * (state < 2 ? (state==0 ? 1 : -1) : 0 );  
  94.         g_moveObbCenter += Vector2(dx, dy);  
  95.         g_moveObbPtr->moveTo(g_moveObbCenter);  
  96.     }  
  97.   
  98.     glutPostRedisplay();  
  99. }  
  100.   
  101. int main(int argc, char *argv[])  
  102. {  
  103.     glutInit(&argc, argv);  
  104.     glutInitDisplayMode(GLUT_RGB|GLUT_DOUBLE|GLUT_DEPTH);  
  105.     glutInitWindowSize(g_window_width, g_window_height);  
  106.     glutCreateWindow(g_app_string);  
  107.   
  108.     // set up world space to screen mapping  
  109.     glMatrixMode(GL_PROJECTION);  
  110.     glLoadIdentity();  
  111.     glOrtho(0.0f, (GLfloat)g_window_size, 0.0f, (GLfloat)g_window_size, -1.0f, 1.0f);  
  112.     glMatrixMode(GL_MODELVIEW);  
  113.     glLoadIdentity();  
  114.     glViewport(0, 0, g_window_size, g_window_size);  
  115.   
  116.     glutDisplayFunc(Render);  
  117.     glutReshapeFunc(Reshape);  
  118.     glutKeyboardFunc(keyboard);  
  119.   
  120.     Init();  
  121.   
  122.     glutMainLoop();  
  123.   
  124.     return 0;  
  125. }  
  126.   
  127. GLenum checkForError(char *loc)  
  128. {  
  129.     GLenum errCode;  
  130.     const GLubyte *errString;  
  131.   
  132.     if ((errCode = glGetError()) != GL_NO_ERROR)  
  133.     {  
  134.         errString = gluErrorString(errCode);  
  135.         printf("OpenGL error: %s",errString);  
  136.   
  137.         if (loc != NULL)  
  138.             printf("(%s)",loc);  
  139.   
  140.         printf("\n");  
  141.     }  
  142.   
  143.     return errCode;  
  144. }  
### Oriented Bounding Box (OBB) 检测实现方法 #### OBB 的定义与特性 方向包围盒(Oriented Bounding Box, OBB)是一种三维或二维空间中的矩形或多边形结构,能够围绕对象旋转并紧密贴合其形状。相比轴对齐包围盒(AABB),OBB 提供更精确的碰撞检测能力,但由于涉及更多的计算操作,在性能上通常不如 AABB[^1]。 #### OBB 碰撞检测的核心原理 OBB 碰撞检测主要依赖分离轴定理(Separating Axis Theorem, SAT)。该理论指出:如果两个凸多边形不重叠,则存在至少一条直线可以将它们完全分开;反之,如果没有这样的直线,则两多边形相交。具体到 OBB 中,SAT 方法通过测试多个可能的分离轴来判断是否存在交叉情况[^3]。 #### 实现步骤概述 以下是 C# 或其他编程语言中实现 OBB 碰撞检测的主要逻辑: 1. **定义数据结构** 定义一个类 `OBB` 来存储盒子的位置、大小以及朝向信息。 2. **构建矩阵变换** 使用旋转和平移矩阵描述每个 OBB 的姿态,并将其应用于顶点坐标系转换过程之中。 3. **投影运算** 将每一对候选面沿潜在分隔矢量投射至一维数轴之上比较区间关系从而判定是否发生冲突事件。 4. **优化策略** 对于大规模场景下的动态物体集合而言,单纯依靠蛮力逐一配对检查效率低下,故需引入层次化加速机制比如四叉树八叉树等辅助索引手段降低总体耗时成本。 下面提供了一个简化版伪代码示例展示基本流程: ```csharp public class Vector3 { public float X { get; set; } public float Y { get; set; } public float Z { get; set; } // ... other methods ... } public struct Matrix3x3 { public Vector3 RowX; public Vector3 RowY; public Vector3 RowZ; // ... constructors and operations ... } public class OBB { public Vector3 Center { get; set; } // center point of the box public Vector3 HalfSize { get; set; } // half-size extents along each axis public Matrix3x3 Orientation { get; set; } // orientation matrix // Methods to transform points between local/world spaces... private List<Vector3> GetAxes() => new List<Vector3>() { this.Orientation.RowX, this.Orientation.RowY, this.Orientation.RowZ }; public bool Intersects(OBB obbOther){ var axes = this.GetAxes(); foreach(var v in obbOther.GetAxes()) axes.Add(v); foreach(Vector3 axis in axes){ if(!TestAxis(axis)) return false; } return true; } private bool TestAxis(Vector3 axis){ double minThis,maxThis,minOther,maxOther; Project(this,axis,out minThis,out maxThis); Project(obbOther,axis,out minOther,out maxOther); if(maxThis<minOther || maxOther<minThis)return false; return true; } void Project(OBB obb,Vector3 axis,out double min,out double max){ // Compute projection interval on given axis. min=double.MaxValue; max=-double.MinValue; for(int i=0;i<8;i++){ var vertex=this.Corner(i); // corner at index 'i' double dotProduct=vertex.Dot(axis); if(dotProduct<min) min=dotProduct; if(dotProduct>max) max=dotProduct; } } } ``` 上述代码片段展示了如何利用分离轴定理来进行 OBB-OBB 碰撞检测的基础框架设计思路[^5]。 --- #### 性能考量与实际应用建议 尽管 OBB 提供了更高的准确性,但在实时渲染环境中仍需要注意平衡精度与帧率之间的取舍问题。例如可以通过预筛选阶段剔除明显不可能接触的目标组群以减少不必要的细粒度验证次数等方式提升整体表现效果[^4]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值