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碰撞检测算法。
         首先来看下头文件:

#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盒的四个顶点,我们也仅仅需要这四个顶点而已。
         下面的几个方法依次来解释下作了哪些工作。

void getAxies()
void OBB::getAxies(VECTOR2* axie)
{
for(int i = 0 ; i < 4 ; i ++)
{
VECTOR2 s ;
Vec2Sub(s,vertex,vertex[(i+1)%4]);
Vec2Normalize(s, s);
axie.x = -s.y ;
axie.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, axie);
if(temp > max)
max = temp ;
else if(temp < min)
min = temp ;
}// end for

return 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);Projection p2 = obb->getProjection(axie1);
if(!p1.overlap(&p2))return false ;}
for(int i = 0 ; i < 4 ; i ++){Projection p1 = getProjection(axie2);Projection p2 = obb->getProjection(axie2);
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算法的实例

#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);//update
void update(float dt);//draw
void 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 Check
CCPoint 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 Check
CCPoint 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 1
ccDrawColor4B(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 2
ccDrawColor4B(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盒。

cocos2d-x碰撞检测学习笔记相关推荐

  1. [cocos2d-x学习笔记][入门基础][塔防]塔防游戏中箭塔旋转并攻击实现

    1. 检测炮塔视线范围内距离它最近的敌人. int length=0; float index=0; for (int i=0; i<Game::allEnemy.size(); i++) { ...

  2. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引...

    Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第十五章:第一人称摄像机和动态索引 原文:Introduction to 3 ...

  3. cocos2d-x学习笔记15:cocos2d-x教程资源总结

    注:cocos2d可作为cocos2dx的参考,两者接口很相似. 名称:知易的<知易Cocos2D-iPhone开发教程> 官方地址:http://blog.sina.com.cn/s/a ...

  4. CososJS学习笔记(1) 环境配置(填坑版,让你少走弯路!)

    这段时间比较了国外的phaser.pixi.create等h5游戏框架以及国内的egret.cocosjs之后,最后还是决定使用cocosjs进行h5游戏的开发. 先简单地说一下个人的观点:首先pix ...

  5. cocos2dx游戏开发——微信打飞机学习笔记(三)——WelcomeScene的搭建

    一.场景与层的关系: cocos2dx的框架可以说主要由导演,场景,层,精灵来构成: 1.其中导演,意如其名,就是操控整个游戏的一个单例,管理着整个游戏. 2.场景就像电影的一幕剧情,所以说,懂得如何 ...

  6. 【计算机网络】学习笔记

    [计算机网络]学习笔记 1. 概论 因特网中的服务 计算机网络特点 计算机网络功能 计算机网络的分类 2. 性能指标 体系结构 ISO/OSI参考模型 3. 物理层 传输媒体 物理层的功能 调制 码元 ...

  7. 千锋Unity学习笔记

    学习笔记:[千锋合集]史上最全Unity3D全套入门教程|匠心之作 文章目录 初级 1.0数学 1.0点乘叉乘 1.点乘: 2.叉乘: 2.0Mathf 3.0Vector 4.0旋转 2.0组件 3 ...

  8. 尚学堂JAVA基础学习笔记_2/2

    尚学堂JAVA基础学习笔记_2/2 文章目录 尚学堂JAVA基础学习笔记_2/2 写在前面 第10章 IO技术 1. IO入门 2. IO的API 3. 装饰流 4. IO实战 5. CommonsI ...

  9. 计算机网络学习笔记(详尽版)

    计算机网络学习笔记 计算机网络 第一章 定义和特点 网络 定义 功能 特点 组成 类别 性能 结构 本章重要概念 第二章 物理层 基本概念和标准 数据通信基础知识 物理层下的传输媒体 引导型传输媒体 ...

最新文章

  1. 用 Winetricks 配置 WINE
  2. spring源码分析之spring-core asm概述
  3. Xcode全局替换内容,一键Replace
  4. android studil打断点_【小技巧】AndroidStudio利用断点打印日志
  5. mysql root 无法建立数据库_MySQL - 在root用户下你跟我说无法建表!?
  6. ros轮式小车学习链接
  7. 全志 移除屏幕超时选项 Patch
  8. DBShop前台RCE
  9. jQuery插件ASP.NET应用之AjaxUpload
  10. 开源|蚂蚁金服开源AntV F2:一个专注于移动,开箱即用的可视
  11. 论文浅尝 - ACL2020 | 用于关系三元组抽取的级联二进制标记框架
  12. 今天研究 Client本来是关联的Expression接口,笔记记录一下。
  13. SSM Controller 页面之间跳转 重定向,有参 无参问题
  14. 手动触发事件_HBase中MemStore的刷写触发机制
  15. jad反编译成java,反编译工具jad的使用(将*.class文件变成*.java文件,附带jad.zip包)...
  16. javaJNI(javah用法)
  17. 苹果x屏幕多少钱_北京苹果手机维修为大家分享iPadAir2换屏幕多少钱
  18. 滴滴:去年协助警方破获25案件 成立打击黑产专项组
  19. csdn竟然还有这种神器!后悔没有早点知道!超好用的csdn插件,别再犹豫了,赶快入手吧!
  20. 洛谷-P1228-地毯填补问题-普及/提高--分治+递归

热门文章

  1. freeBSD安装详细讲解
  2. 12-11 网易实习一面
  3. GitHub标星1w的安卓架构师必备技能,真香
  4. dnf加物理攻击的卡片有哪些_dnf加物理攻击力的宝珠有哪些 dnf物攻宝珠
  5. UltraEdit v21.00
  6. 计算机教学研讨会方案,【教学教研】新学年我校计算机专业组积极开展教学研讨会议...
  7. 兄弟连区块链入门教程分享区块链POW证明代码实现demo
  8. 蓝桥杯算法训练——未名湖边的烦恼 (递归)
  9. java自动化测试语言高级之发送邮件
  10. AMD 显卡/GPU 深度学习折腾指南