引言

最近在与好友聊天的过程中,好友问我如何实现类似这样的游戏。它主要想知道,如何检测旋转过后的物体与其他物体之间的碰撞。

我们知道,在没有旋转的情况下,对于这样的方块,比较规则的物体,我们完全可以使用AABB(Axie-Align Bonding Box)来进行交叉检测,cocos2d-x内置的交叉检测函数也支持这样的功能。但是,在cocos2d-x中,并没有对旋转过后的物体支持进行检测。好友说,它发现经过旋转过后的AABB盒变的比原图要大,的确是这样的。在旋转之后,cocos2d-x内部会重新计算新的AABB盒。而我们知道AABB盒是和坐标轴平行的盒,所以它自然而然变大。(如果读者不知道为什么会变大了,不必深究,这并不是本文的重点)。

想要解决这样的问题,我第一个想到的方案就是使用OBB(Orient Bounding Box)碰撞检测算法来实现。下面就来像大家讲述下,如何在2D空间中实现这样的算法,并且在后面给出大家一个使用cocos2d-x来演示的Demo。

OBB包围盒

OBB,全称是Oriented Bounding Box,也就是带有方向的包围盒。实际上,它和AABB盒一样,也是一个矩形,只不过它具有任意的方向。对OBB进行结构表示,有很多种方法,我在下面的Demo中是使用矩形的四个顶点来定义OBB的。

好了,我们知道了OBB的具体表现形式之后,我们就需要判断两个OBB是否相互碰撞,也就是是否有相互重叠的部分???这里有两种不同的方法来进行。

第一种,我们通过判断OBB包围盒的四个顶点,是否都在另一个OBB盒的四条边定义的正半空间内,这样的方法很简单,感兴趣的同学可以自己去实现下。

另外一种,也是本文将要介绍的方法,称为Seperating Axie Theorem(分离定理),简称为SAT,这是一种一般性的判断基本几何体是否分离的算法。也就是说,对于凸变形,我们都可以使用SAT来判断两个凸多边形是否发生重叠。对于做游戏开发的我们,很有必要掌握这样的理论。

SAT理论解释

对于两个凸多边形,如果他们之间没有发生重叠,那么就是说,存在一个平面能够将这两个物体进行分离。读者可以看下图:

上面的灰线条就表示一个可以将这两个OBB盒分离的平面(读者可以将这条灰线想象成向屏幕里面深入的平面)。

在看下图:

这个图中的白色线条,它垂直于黑色的平面,我们将这个白色的线条称之为分离轴(Seperating Axie)。

在有了分离轴之后,我们需要确定这两个物体是否会发生重叠,也就是说这条潜在的分离轴(注意,这里的分离轴表示的是可能成为这两个OBB之间的分离轴,他们之间是否发生分离,我们需要通过潜在的分离轴来判断是否发生了分离)能够给我们判断两个OBB盒是否分离带来一些便利。

如何通过这条潜在的分离轴来判断这两个OBB盒是否发生了分离了???我们需要将物体在这条分离轴上的投影的起点和终点计算出来,也就是下图中在白色线条上的红线和蓝色线条的起点和终点计算出来:

看到上图,也就是我们需要将A,B,C,D这四个点计算出来。当然,读者需要注意,我这里说的点是在白色直线这条轴上的点,也就是说,它实际上就是一个值而已,它的参考坐标系是这条白线,所以他的值可能是0,-1,10,这样的一个整数值,而并不是像二维空间中的点(29,9)这样的。实际上,这几个点最终的值是多少,我们并不关心,我们只关心的是他们相对之间的位置关系,而这样的关系只要他们是用同一个参考系进行描述的即可。

如果计算这样的投影点???我们知道,OBB盒是由四个顶点组成的,所以,我们只要用这四个顶点与这条分离轴进行点积Dot运算,就可以得到这样的一个投影值,而这个投影值是否是投影线端点的值,我们需要通过判断,来判断它是否是最大的或者最小的值就可以确定了。

在计算出了这四个点的值之后,我们只要判断,红线和蓝线是否发生了重叠即可。

好了,我们知道了如果通过一条潜在的分离轴来判断这条分离轴是否真实的分离了这两个OBB盒。那么,剩下的问题是,一共有多少个这样的潜在的分离轴?我们如何求得?

对于这样的问题,是经过专家研究过后得出的结果,至于为什么是这样的,读者可以自己深入的研究这个理论之后,来了解,所以,这里将直接给出结论:

对于两个OBB盒来说,他们之间潜在的分离轴就是他们边的法线,也就是下图中白色箭头表示的轴向:

好了,还有问题,就是如何求得这些边的法线了???这个很简单,我们假设,下面是某一条边的两个顶点:

顶点一:(10,0) 顶点二: (20,-20)

我们用顶点一 减去 顶点二 (或者顶点二 减去 顶点一,都一样)得到(-10, 20)

然后我们只要将(-10, 20)中的x,y分量位置调反,然后任意一个去相反值就可以了,也就是说有两个(20,10)或者(-20,-10),随便选取哪一个做为分离轴都可以,还记得我在前面说过,最终计算出来的值是多少无所谓,只要他们选取的分离轴是同一个,那就能够通过他们在这条分离轴上的值来判断他们之间的位置关系,我们需要的仅仅是位置关系而已。

好了,SAT的理论知识就到这里了。如果你想深入了解SAT理论,并且想知道在3D空间中的情况(注意,3D空间中的SAT盒潜在的分离轴要多的多,而且判断方法也不尽相同),可自行学习相关理论知识。

程序实例

下面就来介绍一个我自己实现的一种简化的OBB碰撞检测算法。

首先来看下头文件:

//------------------------------------------------------------------------------
// declaration  : Copyright (c), by XJ , 2014 . All right reserved .
// brief        : This file will define the OBB(Oriented bounding box)
// author       : XJ
// date         : 2014 / 6 / 16
// version      : 1.0
//------------------------------------------------------------------------------
#pragma once
#include"XJMath.h"class Projection
{
public:Projection(float min, float max);~Projection();public:bool overlap(Projection* proj);public:float getMin() const;float getMax() const ;private:float min ;float max ;
};class OBB
{
public:OBB();~OBB();public:void getAxies(VECTOR2 * axie);Projection getProjection(VECTOR2 axie);public:VECTOR2 getVertex(int index) const;void setVertex(int index, float x, float y);void setVertex(int index, VECTOR2 v);public:bool isCollidWithOBB(OBB* obb);private:VECTOR2 vertex[4] ;
};

先来看看OBB的类。这个类中的成员属性只有一个:

VECTOR2 vertex[4] ;

正如我前面说的,这个成员数组保存了OBB盒的四个顶点,我们也仅仅需要这四个顶点而已。

下面的几个方法依次来解释下作了哪些工作。

void getAxies()

void OBB::getAxies(VECTOR2* axie)
{for(int i = 0 ; i < 4 ; i ++){VECTOR2 s ;Vec2Sub(s,vertex[i],vertex[(i+1)%4]);Vec2Normalize(s, s);axie[i].x = -s.y ;axie[i].y = s.x ;}
}

这个函数很简单,只是简单的根据我们前面讨论的结果,计算OBB盒的四条边的四个分离轴,你可以看到我这里使用的法向量计算方法是这样的:

如果边向量为(x,y)那么法向量为(-y,x),你也可以设置为(y,-x)。结果没有什么变化。

Projection getProjection(VECTOR2 axie)

Projection OBB::getProjection(VECTOR2 axie)
{float min = 0 ;Vec2Dot(min,vertex[0], axie);float max = min ;for(int i = 1 ; i < 4 ; i ++){float temp = 0 ;Vec2Dot(temp, vertex[i], axie);if(temp > max)max = temp ;else if(temp < min)min = temp ;}// end forreturn Projection(min, max);
}

这里的方法是计算出一个投影线条出来,也就是在前面图中画出来的红线和蓝线。投影结构我使用Projection来表示,在上面你可以看到Projection的定义。它很简单,只是保存了两个float形的数据,分别表示OBB盒在分离轴上投影的最小值和最大值。计算最小值和最大值的算法在上面的getProjection中给出,我们需要根据指定的分离轴axie来计算对应的投影。

vertex的方法

OBB类中,还存在几个setter和getter的方法,这几个方法只是简单的获取和设置里面的值而已。没有什么好解释的。

bool isCollidWithOBB(OBB* obb)

bool OBB::isCollidWithOBB(OBB* obb)
{VECTOR2 * axie1 = new VECTOR2[4];VECTOR2 * axie2 = new VECTOR2[4];//Get the seperat axiegetAxies(axie1);obb->getAxies(axie2);//Check for overlap for all of the axiesfor(int i = 0 ; i < 4 ; i ++){Projection p1 = getProjection(axie1[i]);Projection p2 = obb->getProjection(axie1[i]);if(!p1.overlap(&p2))return false ;}for(int i = 0 ; i < 4 ; i ++){Projection p1 = getProjection(axie2[i]);Projection p2 = obb->getProjection(axie2[i]);if(!p1.overlap(&p2))return false ;}delete[]axie1 ;delete[]axie2 ;return true ;
}

这个方法,就是对传递进来的OBB判断是否与调用这个方法的OBB发生了交叉。很简单,我们只要求出每一个OBB的四个分离轴,然后调用每一个OBB的投影方法,计算出在这8个分离轴上的投影,最后调用投影的overlap方法,如下所示:

bool Projection::overlap(Projection* proj)
{if(min > proj->getMax()) return false ;if(max < proj->getMin()) return false ;return true ;
}

来判断投影是否发生了交叉。一旦我们找到了一个分离轴,也就是他们在这个轴上的投影是不相交的,那么我们就可以确定,这两个OBB盒是不相交的,就可以提早退出这个函数了。如果对这8个潜在分离轴的判断都失败了,也就表示,在这8个轴上都发生了交叉,那么就可以确定,这两个OBB盒一定发生了碰撞或者重叠。

上面是这个OBB碰撞检测算法的核心内容。下面在给出一个使用这个OBB算法的实例,我是使用cocos2d-x来制作的Demo。代码如下:

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__#include "cocos2d.h"
#include "OBB.h"class HelloWorld : public cocos2d::CCLayer
{
public:// Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphonevirtual bool init();  // there's no 'id' in cpp, so we recommand to return the exactly class pointerstatic cocos2d::CCScene* scene();// a selector callbackvoid menuCloseCallback(CCObject* pSender);// implement the "static node()" method manuallyCREATE_FUNC(HelloWorld);//updatevoid update(float dt);//drawvoid draw();private:cocos2d::CCSprite* m_Sprite1 ;cocos2d::CCSprite* m_Sprite2 ;float m_vx1 ;float m_vy1 ;float m_vx2 ;float m_vy2 ;OBB* obb1 ;OBB* obb2 ;};#endif  // __HELLOWORLD_SCENE_H__
#include "HelloWorldScene.h"
#include "OBB.h"using namespace cocos2d;CCScene* HelloWorld::scene()
{CCScene * scene = NULL;do {// 'scene' is an autorelease objectscene = CCScene::create();CC_BREAK_IF(! scene);// 'layer' is an autorelease objectHelloWorld *layer = HelloWorld::create();CC_BREAK_IF(! layer);// add layer as a child to scenescene->addChild(layer);} while (0);// return the scenereturn scene;
}// on "init" you need to initialize your instance
bool HelloWorld::init()
{bool bRet = false;do {//// super init first//CC_BREAK_IF(! CCLayer::init());this->scheduleUpdate();m_Sprite1 = CCSprite::create("Fog_x4.png");m_Sprite1->setPosition(ccp(0,160));m_Sprite1->retain();this->addChild(m_Sprite1);m_Sprite1->runAction(CCRepeatForever::create(CCRotateBy::create(1.0/60, 1)));m_Sprite2 = CCSprite::create("Fog_x4.png");m_Sprite2->setPosition(ccp(240,160));m_Sprite2->retain();this->addChild(m_Sprite2);m_Sprite2->runAction(CCRepeatForever::create(CCRotateBy::create(1.0/60, 1)));m_vx1 = 2 ;m_vy1 = -2 ;m_vx2 = -2 ;m_vy2 = 2 ;obb1 = new OBB();obb2 = new OBB();bRet = true;} while (0);return bRet;
}void HelloWorld::menuCloseCallback(CCObject* pSender)
{// "close" menu item clickedCCDirector::sharedDirector()->end();
}void HelloWorld::update(float dt)
{//Boundarying CheckCCPoint pt1 = m_Sprite1->getPosition();if(pt1.x < 0 || pt1.x > 480)m_vx1 = -m_vx1 ;if(pt1.y < 0|| pt1.y > 320)m_vy1 = -m_vy1 ;CCPoint pt2 = m_Sprite2->getPosition();if(pt2.x < 0 || pt2.x > 480)m_vx2 = -m_vx2 ;if(pt2.y < 0|| pt2.y > 320)m_vy2 = -m_vy2 ;pt1.x += m_vx1 ;pt1.y += m_vy1 ;m_Sprite1->setPosition(pt1);pt2.x += m_vx2 ;pt2.y += m_vy2 ;m_Sprite2->setPosition(pt2);//Collision CheckCCPoint pt = m_Sprite1->convertToWorldSpace(ccp(0,0));obb1->setVertex(0, pt.x, pt.y);pt = m_Sprite1->convertToWorldSpace(ccp(64,0));obb1->setVertex(1, pt.x, pt.y);pt = m_Sprite1->convertToWorldSpace(ccp(64,64));obb1->setVertex(2, pt.x, pt.y);pt = m_Sprite1->convertToWorldSpace(ccp(0,64));obb1->setVertex(3, pt.x, pt.y);pt = m_Sprite2->convertToWorldSpace(ccp(0,0));obb2->setVertex(0, pt.x, pt.y);pt = m_Sprite2->convertToWorldSpace(ccp(0,64));obb2->setVertex(1,pt.x, pt.y);pt = m_Sprite2->convertToWorldSpace(ccp(64,64));obb2->setVertex(2, pt.x, pt.y);pt = m_Sprite2->convertToWorldSpace(ccp(64,0));obb2->setVertex(3, pt.x, pt.y);if(obb1->isCollidWithOBB(obb2)){VECTOR2 collision = MAKE_VECTOR2(pt1.x - pt2.x, pt1.y - pt2.y);Vec2Normalize(collision, collision);Vec2Mul(collision, collision, 2.8);m_vx1 = collision.x ;m_vy1 = collision.y ;m_vx2 = -collision.x ;m_vy2 = -collision.y ;}
}void HelloWorld::draw()
{//Draw the OBB of 1ccDrawColor4B(255,0,0,255);ccDrawLine(ccp(obb1->getVertex(0).x, obb1->getVertex(0).y),ccp(obb1->getVertex(1).x, obb1->getVertex(1).y));ccDrawLine(ccp(obb1->getVertex(1).x, obb1->getVertex(1).y),ccp(obb1->getVertex(2).x, obb1->getVertex(2).y));ccDrawLine(ccp(obb1->getVertex(2).x, obb1->getVertex(2).y),ccp(obb1->getVertex(3).x, obb1->getVertex(3).y));ccDrawLine(ccp(obb1->getVertex(0).x, obb1->getVertex(0).y),ccp(obb1->getVertex(3).x, obb1->getVertex(3).y));//Draw the OBB of 2ccDrawColor4B(0,255,0,255);ccDrawLine(ccp(obb2->getVertex(0).x, obb2->getVertex(0).y),ccp(obb2->getVertex(1).x, obb2->getVertex(1).y));ccDrawLine(ccp(obb2->getVertex(1).x, obb2->getVertex(1).y),ccp(obb2->getVertex(2).x, obb2->getVertex(2).y));ccDrawLine(ccp(obb2->getVertex(2).x, obb2->getVertex(2).y),ccp(obb2->getVertex(3).x, obb2->getVertex(3).y));ccDrawLine(ccp(obb2->getVertex(0).x, obb2->getVertex(0).y),ccp(obb2->getVertex(3).x, obb2->getVertex(3).y));
}

需要指出的是,我们需要将在cocos2d-x中进行旋转后的精灵的坐标设置到OBB中。读者可以发现,我在update方法中,使用了大量的convertToWorldSpace函数。这个函数,可以将旋转过后的精灵的相对于局部坐标的顶点转化为世界坐标的顶点。通过精灵调用convertToWorldSpace,在参数中指定需要转化的点,而我只需要矩形的四个顶点,他们分别是(0,0),(64,0),(0,64),(64,64)。图片尺寸是64*64的。通过convertToWorldSpace就可以计算出来了。

在draw方法中,我为了让Demo看上去更容易看出发生碰撞,我将OBB盒用直线绘制出来,你可以在截图中发现,每一个Fog周围都有一个框框,这个就是OBB盒。

程序源码即程序可以在下面的链接中下载:

OBB.zip

基于cocos2d-x的2D空间中的OBB(Orient Bounding Box)碰撞检测算法相关推荐

  1. 基于Bounding Box的激光点云聚类

    Camera系列文章 传感器融合是将多个传感器采集的数据进行融合处理,以更好感知周围环境:这里首先介绍Camera的相关内容,包括摄像头及图像知识基本介绍,OpenCV图像识别(特征提取,目标分类等) ...

  2. Image2StyleGAN:如何将图像嵌入到 StyleGAN 潜在空间中?

    目录 摘要: 1. 介绍 2. 相关工作 ①高质量GAN ②潜在空间嵌入 ③感知损失和样式转换 3. 哪些图像可以嵌入到 StyleGAN 潜在空间中? 3.1 嵌入各种图像类别的结果 3.2脸部图像 ...

  3. 基于 2D 激光雷达和实时回环优化的 SLAM 算法

    基于 2D 激光雷达和实时回环优化的 SLAM 算法 原文: https://www.ixueshu.com/document/771d692c7d3c0c40318947a18e7f9386.htm ...

  4. 数据表从一个表空间中移动到另一个表空间中

    数据表从一个表空间中移动到另一个表空间中 1) alter table [table_name] move tablespace [new tablespace]; 移动LOB字段 2) alter ...

  5. 【一元多项式算法】设一个一元多项式采用带头结点的单链表存储,所有结点 按照升幂方式链接。设计一个算法,求两个多项式 A 和 B 的乘积,结果多项式 C 存放在新辟的空间中。

    [一元多项式算法]设一个一元多项式采用带头结点的单链表存储,所有结点 按照升幂方式链接.设计一个算法,求两个多项式 A 和 B 的乘积,结果多项式 C 存放在新辟的空间中. #include<s ...

  6. 基于深度学习的2D图像目标检测

    参见第一部分网址1,第二部分网址2 目前学术和工业界出现的目标检测算法分成3类:(参见一文读懂目标检测:R-CNN.Fast R-CNN.Faster R-CNN.YOLO.SSD) 1. 传统的目标 ...

  7. MySQL的InnoDB存储引擎中,缓冲池中的Changer Buffer与系统表空间中的Changer Buffer的关系

    MySQL的InnoDB存储引擎中,缓冲池中和系统表空间中都存在Changer Buffer,那它们之间的关系是怎样的呢?先来一张InnoDB存储引擎的架构图: 翻阅了MySQL官网发现如下: 1.h ...

  8. 计算机推测四维空间,4维空间真的存在吗?科学家推测未来人类可能会生活在4维空间中...

    原标题:4维空间真的存在吗?科学家推测未来人类可能会生活在4维空间中 看过刘慈欣著名科幻小说<三体>的人都知道,在"死神永生"章节中,人类文明甚至太阳系文明的最后毁灭是 ...

  9. 基于平面的约束2D激光雷达和相机的联合标定(2D Laser and Camera Calibration )原理及项目代码具体使用——旷视

    1 基于平面的约束2D激光雷达和相机的联合标定(2D Laser and Camera Calibration )原理 这是旷视做的一个关于2D激光雷达和相机的联合标定算法,在看这个标定算法之前,你可 ...

最新文章

  1. php禁止网页抓取,服务器反爬虫攻略:Apache/Nginx/PHP禁止某些User Agent抓取网站 - 龙笑天下...
  2. STM32CubedMX 下载与安装
  3. 图解notepad++插件使用
  4. 京东秒杀系统模块的Redis分布式锁深度剖析,没给你讲明白你打我
  5. Matplotlib基础(part1)--基本绘图
  6. 一种简单的不需要查询UI5文档就能获得所有API的小技巧
  7. vue-cli 打包
  8. android有关医疗实现功能,基于Android的智能医疗监测系统的设计与实现_问答库
  9. python win32转pdf 横版_python实现word转pdf
  10. 对 React 组件进行单元测试(unit testing)
  11. bugzilla dbd-mysql_Windows上配置bugzilla.doc
  12. 新手小白学JAVA API手册使用方式说明
  13. html5ie11缩放,IE 11 页面缩放后再次打开不能保存之前的缩放比例
  14. 易基因|作物育种:MdMTA介导的RNA甲基化(m6A修饰)在苹果抗逆品种选育中的作用研究
  15. 如何将flv转换成mp3格式
  16. android 自定义接口,Android中定义接口的用法
  17. [转]Form中控制Tab画布不同标签间切换的方法
  18. Fluent的porous jump边界条件
  19. 霍兰德人格分析雷达图的两个坑
  20. 35+的互联网程序员,人都哪去了?

热门文章

  1. 一文带你深入浅出C语言数据
  2. 1.企业信息化战略与实施
  3. PAT 1026 Table Tennis (30分)
  4. 安卓day29网络编程 HttpClient AsyncHttpClient 断点续传多线程下载器 HttpUtils
  5. 人脑的计算能力有多强?
  6. 陈林接替张一鸣任今日头条CEO 字节跳动学谷歌进行架构升级
  7. 数据中台 画像标签_中台产品经理必懂(4):数据中台标签系统
  8. phpcms选择文件无法加载插件怎么办_win7系统下浏览器显示无法加载插件如何解决...
  9. python使用mysql实例教程_Python操作Mysql实例代码教程在线版(查询手册)_python
  10. 菜鸟直播挑战:一小时创作多人游戏