2D多边形碰撞检测
介绍
这是一篇论证如何在2D动作游戏中执行碰撞检测的文章(Mario,宇宙入侵者等),为了保证它的高效性和精确性,碰撞检测是以多边形为基础的,而不是以sprite为基础。这是两种不同的设计途径。
基于sprite的检测执行的是检测sprites的像素的交叉,以这种方式来检测碰撞。多边形是使用向量数学来精确的计算点,时间和碰撞的方向。当多边形只是一种近似sprite自身的时候,它就超越了sprite系统。
表现出了更精确的物理现象,例如弹性,摩擦,在一个随机的形状中来处理斜坡。
碰撞检测相对一个高速的sprite来讲是更加的精确的。在一个基于sprite的系统中,对象有可以在一个强力的跳跃中因为速度太快而相互穿越。
这是一个基于向量数学的系统,所以可以扩展到3D,但是一个sprite碰撞系统被完全的限制到了2D中。
特色:
因为运算法则的限制,系统只能操作凸多边形,例如三角形,矩形,五边形,圆形。在一个非凸多边形中,你可以将这些多边形分成三角形来处理。
无论是快速或者慢速的多边形,他的处理方式都是一样的。不管对象的移动有多快,碰撞也不会丢失的。他也可以来处理交叠,分开已经交叉的对象。
里面的范例也有线段的交叉。这可以用来模拟子弹。
它也提供了一些简单的物理系统,模拟弹力,一些基本的摩擦力和静摩擦力。在斜坡上的一些特性它也可以模拟。
这里也有一个刚体系统的例子,引用的是Chrsi Hecker的物理文章。
局限性:
碰撞的种类,我的意思是它不能顺序的处理碰撞。在一个快速的对象中这可能会有问题。一旦一个碰撞被检测了,它便会直线的离开。你可以检测到第一个碰撞并处理它,然后再找到其他的碰撞。但是在一个2D的动作游戏中,这是很具有杀伤力的。
必要条件:
一些普通的原料,一个编译器,范例是基于GLUT(GL开发包)的框架,所以需要下载一个小的GLUT SDK和一个兼容opengl的显卡。
你还需要对向量数学有一个基本的了解。这里的文章不准备对点乘进行讲解,但是它可以帮助你对向量的操作有更好的了解。
对于刚体,你需要对Chris Hecker的文章仔细的阅读一下。它需要你付出更多,但是它是值得的,并不是多么的困难。
内容列表:
1:分离轴的方法
2:扩展应用于碰撞反馈的分离轴的方法。
分离轴的方法:
这是碰撞检测的核心,原理是非常简单的,也非常的容易实现。它也是快速和稳定的,因为没有除法在运算中被使用。我将会带给大家一个简单的两个BOX碰撞测试的例子。

该运算法则试着来确定两个对象中的一个合适的面。如果有这样的一个面存在,如果有这样的面存在,说明对象是分离的,没有交叉。
为了确定对象是否是分离的。只要将这个对象投影到这个平面的法线上,然后比较它们之间的间隔看他们是否交叉。
所以,很明显有无数个边能够适合这两个分离的对象,但是已经证明你只要测试少数的几个面就可以了,对于上面图形中的两个BOX,你可以看到边的法线是形状B的边。
从上面的这些形状中可以发现,那些即将被测试的分离面是两个BOX的边的法线。对于这两个BOX而言,你只需要测试4个分离的面。在这4个面中,一旦你发现一个分离面分隔了这两个BOX。你就可以知道了这两个BOX是分离的。然后返回一个没有碰撞的标记。
如果这4个面没有分隔这两个BOX,那么这两个BOX肯定是交叉的,说明他们有一个碰撞。
扩展到一般的多边形,该运算法则仍然是适用的。只不过是测试的边变化了。那些分离的面的法线是和每个多边形的边的垂直方向是平行的。在下面的图形中,你可以看到有两个分离面被测试。在红色的面被测试的时候,你可以看到那两个间隔是交叉的,但是在蓝色的被测试的时候,间隔并没有交叉。所有蓝色的面是分离面,对象因此是没有交叉的。

现在,我们有一个算法来检测两个多边形是否是相交的。代码可以分为三个部分:

a)         生成需要测试的分离轴

b)        计算每一个多边形在分离轴法线上的投影

c)        检测这些投影是否相交

bool Intersect(Polygon A, Polygon B)
{
     for(I = 0; I < A.num_edges; I ++)
     {
           Vector N = Vector(-A.EdgeDir[I].y, A.EdgeDir[I].x);
           if (AxisSeparatePolygons(N, A, B))
                 return false;
      } 
      for(I = 0; I < B.num_edges; I ++) 
      { 
            Vector N = Vector(-B.EdgeDir[i].y, B.EdgeDir[I].x); 
            if (AxisSeparatePolygons (N, A, B)) 
                  return false; 
      } 
      return true; 
}

void CalculateInterval(Vector Axis, Polygon P, float& min, float& max) 

      float d = Axis dot P.vertex[0]; 
      min = max = d; 
      for(I = 0; I < P.num_vertices; I ++) 
      { 
            float d = P.vertex[I] dot Axis; 
            if (d < min) 
                  min = d; 
            else 
                  if(d > max) 
                        max = d; 
      } 
}

到这里,这个计算两个2D多边形碰撞的运算法则是非常快和稳定的。边的方向不一定是单位化的,所以你可以避免将边的方向都存储起来,你可以直接根据多边形的顶点来构建这些边的方向。

for(J = A.num_vertices-1, I = 0; I < A.num_vertices; J = I, I ++) 

      Vector E = A.vertex[I] – A.vertex[J]; 
      Vector N = Vector(-E.y, E.x);

if (AxisSeparatePolygons(N, A, B)) 
            return false; 
}

扩展用于碰撞反馈的分离轴的方法:
检测多边形是否碰撞是非常有用处的,但是我们可以做的更多。当多边形交叉后,我希望能够让多边形分离来制止他们交叉。
分隔轴的方法还是要被用到的,通过作一个细微的额外工作。他能够返回他穿刺的深度和方向,并以此来分离它们。一个交叉的深度和方向的结合体也被称为MTD,或者是最小的转换距离。这个最小的向量需要让对象分离来制止他们交叉。
我们可以借助于分离轴来计算MTD。
当对象交叉的时候,我们知道了两个对象的交叉的每个分离轴的间隔距离。沿着这个轴的两个间隔的交叉的量提供了一个推进向量,你需要将其应用于这两个对象来让这些对象的投影来制止他们在这个轴上的交叉。

"推力向量"是一个向量,它的作用是你需要应用到A中以便制止他同B交叉。
很明显,你不能让这个对象沿着一个随机的轴来分离。待选的这些轴中应该选择那个交叉的间隔最小的轴。这个推力向量提供了一个最小的转换的距离。

bool Intersect(Polygon A, Polygon B, Vector& MTD) 

       // potential separation axes. they get converted into push 
       vectors Vector Axis[32]; 
       // max of 16 vertices per polygon 
       int iNumAxis = 0; 
       for(J = A.num_vertices–1, I = 0; I < A. num_vertices; J = I, I ++) 
       { 
              Vector E = A.vertex[I] – A.vertex[J]; 
              Axis[iNumAxis++] = Vector(-E.y, E.x); 
       
       if (AxisSeparatePolygons(N, A, B)) 
                     return false; 
       } 
       for(J = B. num_vertices–1, I = 0; I < B.num_vertices; J = I, I ++) 
       { 
              Vector E = B.vertex[I] – B.vertex[J]; 
              Axis[iNumAxis++] = Vector(-E.y, E.x); 
       
              if (AxisSeparatePolygons (N, A, B)) 
                     return false; 
       } 
       
       // find the MTD among all the separation vectors 
       MTD = FindMTD(Axis, iNumAxis);

// makes sure the push vector is pushing A away from B 
       Vector D = A.Position – B.Position; 
       if (D dot MTD < 0.0f) 
              MTD = -MTD;

return true; 
}

bool AxisSeparatePolygons(Vector& Axis, Polygon A, Polygon B) 

       float mina, maxa; 
       float minb, maxb;

CalculateInterval(Axis, A, mina, maxa); 
       CalculateInterval(Axis, B, minb, maxb);

if (mina > maxb || minb > maxa) 
              return true;

// find the interval overlap 
       float d0 = maxa - minb; 
       float d1 = maxb - mina; 
       float depth = (d0 < d1)? d0 : d1;

// convert the separation axis into a push vector (re-normalise 
       // the axis and multiply by interval overlap) 
       float axis_length_squared = Axis dot Axis;

Axis *= depth / axis_length_squared; 
       return false; 
}

Vector FindMTD(Vector* PushVectors, int iNumVectors) 

       Vector MTD = PushVector[0]; 
       float mind2 = PushVector[0] dot PushVector[0]; 
       for(int I = 1; I < iNumVectors; I ++) 
       { 
              float d2 = PushVector[I] * PushVector[I]; 
              if (d2 < mind2) 
              { 
                     mind2 = d2; 
                     MTD = PushVector[I]; 
              } 
       } 
       return MTD; 
}

当对象交叉的情况下,你知道了MTD向量,分离他们就简单了。

A.Postion += MTD * 0.5f;
B.Position -= MTD * 0.5f;

这只不过是一个投影数学。如果间隔是分离的,我们就需要计算一下这两个间隔接触的时间。
比较一下那个"静态"的分离轴的计算方法,有一个额外的轴我们需要测试,这很明显是一个相对于位移向量(速度向量)的轴。
所以对于每个分离轴,有三种选择。
间隔重叠
间隔分离,但是在未来的某个时间会重叠。
间隔分离,但是在未来的某个时间不会重叠或者是碰撞的很晚。
第3个选项的意思是对象不会在这帧上碰撞,分离轴真正的分离里这些对象。在这两个对象之间在这一帧上没有任何的碰撞。
AxisSeparatePolygon()函数返回交叠的量或者碰撞的时间,为了区分这两种情况,当一个交叠被发现的时候,一个负数被返回,如果在将来一个碰撞被检测到了,一个整数被返回的,函数类似下列的函数:

bool AxisSeparatePolygons(Vector Axis, Polygon A, Polygon B, Vector Offset, Vector Vel, float& t, float tmax);

Offset是A和B的相对的位置,Vel是A和B的相对的速度。
当找到碰撞面的时候,对于MTD是非常容易被找到的,但是对于在未来的某个时间的碰撞则优先于交叉的,如果碰撞在未来的某个时间被发现,最近一个会被选择。
如果什么都没有发现,我只处理交叉,象以前那样,最小的交叠会被使用。
碰撞计算的函数然后返回碰撞的法线,碰撞的深度(一个负数)或者碰撞的时间(一个正数)。
最终的伪代码如下:

bool Collide(     const Vector* A, int Anum, 
                            const Vector* B, int Bnum, 
                            const Vector& xOffset, const Vector& xVel, 
                            Vector& N, float& t) 

       if (!A || !B) return false; 
            
       // All the separation axes 
       // note : a maximum of 32 vertices per poly is supported 
       Vector xAxis[64]; 
       float taxis[64]; 
       int iNumAxes=0;

xAxis[iNumAxes] = Vector(-xVel.y, xVel.x); 
       float fVel2 = xVel * xVel; 
       if (fVel2 > 0.00001f) 
       { 
              if (!IntervalIntersect( A, Anum, B, Bnum, xAxis[iNumAxes], xOffset, xVel, taxis[iNumAxes], t)) 
                      return false; 
              iNumAxes++; 
       }

// test separation axes of A 
       for(int j = Anum-1, i = 0; i < Anum; j = i, i ++) 
       { 
              Vector E0 = A[j]; 
              Vector E1 = A[i]; 
              Vector E = E1 - E0; 
              xAxis[iNumAxes] = Vector(-E.y, E.x); 
              
              if (!IntervalIntersect( A, Anum, B, Bnum, xAxis[iNumAxes], xOffset, xVel, taxis[iNumAxes], t)) 
                     return false;

iNumAxes++; 
       }

// test separation axes of B 
       for(int j = Bnum-1, i = 0; i < Bnum; j = i, i ++) 
       { 
              Vector E0 = B[j]; 
              Vector E1 = B[i]; 
              Vector E = E1 - E0; 
              xAxis[iNumAxes] = Vector(-E.y, E.x);

if (!IntervalIntersect( A, Anum, B, Bnum, xAxis[iNumAxes], xOffset, xVel, taxis[iNumAxes], t)) 
                     return false;
              iNumAxes++;
       }
       
       if (!FindMTD(xAxis, taxis, iNumAxes, N, t)) 
              return false;

// make sure the polygons gets pushed away from each other. 
       if (N * xOffset < 0.0f) 
              N = -N;

return true; 
}

bool AxisSeparatePolygons ( Vector N, Polygon A, Polygon B, Vector Offset, Vector Vel, float &t, float tmax) 

       float min0, max0; 
       float min1, max1;

CalculateInterval(N, A, min0, max0); 
       CalculateInterval(N, B, min1, max1); 
       
       float h = Offset dot N; 
       min0 += h; 
       max0 += h;

float d0 = min0 - max1; // if overlapped, do < 0 
       float d1 = min1 - max0; // if overlapped, d1 > 0

// separated, test dynamic intervals 
       if (d0 > 0.0f || d1 > 0.0f) 
       { 
              float v = Vel dot N;

// small velocity, so only the overlap test will be relevant. 
              if (fabs(v) < 0.0000001f) 
                     return false;

float t0 =-d0 / v; // time of impact to d0 reaches 0 
              float t1 = d1 / v; // time of impact to d0 reaches 1 
              // sort the times. 
              if (t0 > t1) 
              { 
                     float temp = t0; 
                     t0 = t1; 
                     t1 = temp; 
              } 
              // take the minimum positive 
              taxis = (t0 > 0.0f)? t0 : t1;

// intersection time too late or back in time, no collision 
              if (taxis < 0.0f || taxis > tmax) 
                     return true;

return false; 
       } 
       else 
       { 
              // overlap. get the interval, as a the smallest of |d0| and |d1| 
              // return negative number to mark it as an overlap 
              taxis = (d0 > d1)? d0 : d1; 
              return false; 
       } 
}

bool FindCollisionPlane (Vector* Axis, float* taxis, int iNumAxes, Vector& Ncoll, float& tcoll) 

       // find collision first 
       int mini = -1; 
       tcoll = 0.0f; 
       for(int i = 0; i < iNumAxes; i ++) 
       { 
              if (taxis[i] > 0.0f) 
              { 
                     if (taxis[i] > tcoll) 
                     { 
                            mini = i; 
                            tcoll = taxis[i]; 
                            Ncoll = Axis[i]; 
                            Ncoll.Normalise(); // normalise axis 
                     }
              }
       }

// found a collision 
       if (mini != -1) 
              return true;

// nope, find overlaps 
       mini = -1; 
       for(int i = 0; i < iNumAxes; i ++) 
       { 
              float n = Axis[i].Normalise(); // axis length

taxis[i] /= n; // normalise interval overlap too

// remember, those numbers are negative, so take the closest to 0 
              if (mini == -1 || taxis[i] > tcoll) 
              { 
                     mini = i; 
                     tcoll = taxis[i]; 
                     Ncoll = Axis[i]; 
              } 
       } 
       
       return (mini != -1); 
}

这就是全部了,一个检测多边形的碰撞的系统返回一个未来的时间或者是当交叉的时候返回那个碰撞的面。

H5游戏开发之多边形碰撞检测的更多相关文章

  1. 今天我看了一个H5游戏EUI的例子,我都快分不清我到底是在用什么语言编译了代码了,作为刚刚学习H5游戏开发的菜鸟只能默默的收集知识

    今天看了一个EUI的demo,也是接触H5游戏开发的第五天了,我想看看我能不能做点什么出来,哎,自己写果然还是有问题的.在看EUI哪一个demo的时候就遇见了一些摇摆不定的问题,我觉得提出来 1.to ...

  2. 最近这两天看了关于H5游戏开发的一个教程,实践很短暂,看了很多理论的东西,现在呢也只是想回忆回忆关于EUI的部分知识吧

    首先我了解了什么是Egret: Egret中文就是白鹭的意思,Egret是一套H5游戏开发的软件.(纯粹属于个人理解) 其次我对以下几款软件的相关知识做了些了解: Egret Engine(引擎),E ...

  3. 关于h5游戏开发,你想了解的一切都在这儿!

    ​2020年,受疫情影响,线下产业红利褪去,线上迎来的新一轮的高峰.众多商家纷纷抓住了转型时机,开启了流量争夺战.H5游戏定制无疑是今年引流的大热门.如何开发一款有趣.有爆点.用户爱买单的好游戏呢? ...

  4. 为什么选择H5游戏开发定制?

    为什么选择H5游戏开发定制? 随着微信H5游戏推广带来的显著效果,越来越多的商家已经加入到游戏营销的队伍中来, 对H5小游戏有了解的商家都知道,[模板游戏]的价格往往低于[定制游戏]的价格,可是为什么 ...

  5. H5游戏开发之抓住小恐龙

    第一次写技术性博文,以前都只是写一些生活感想,记录一些生活发生的事情. 博主大三学生一枚,目前学习JS一年多,还处于学习阶段,有什么说的不好的希望大牛指点下,由于第一次写博文,排版什么的有待改进,希望 ...

  6. 【读书笔记《Android游戏编程之从零开始》】18.游戏开发基础(碰撞检测)

    1.矩形碰撞 所谓矩形碰撞,就是利用两个矩形之间的位置关系来进行判断,如果矩形的像素在另外一个矩形之中,或者之上都可以认为这两个矩形发生了碰撞. 如果单纯的去考虑哪些情况会判定两个矩形发生碰撞,倒不如 ...

  7. H5游戏开发:贪吃蛇

    贪吃蛇的经典玩法有两种: 积分闯关 一吃到底 第一种是笔者小时候在掌上游戏机最先体验到的(不小心暴露了年龄),具体玩法是蛇吃完一定数量的食物后就通关,通关后速度会加快:第二种是诺基亚在1997年在其自 ...

  8. 【h5游戏开发】egret引擎p2物理引擎 - 小球碰撞地面搞笑的物理现象

    重力的方向和地面的问题 p2中默认的方向是从上到下,如果重力默认是正数的话,物体放到世界中是会从上面往下面飘的 p2中plane地面默认的方向是y轴的方向,而在p2中y轴的方向默认是从上往下 首先来看 ...

  9. 开发H5游戏引擎的选择:Egret或Laya?

    开发H5游戏引擎的选择:Egret或Laya? 一.总结 一句话总结:选laya吧 二.开发H5游戏引擎的选择:Egret或Laya? 一.H5游戏开发的引擎介绍 开发H5游戏的引擎有很多,比如egr ...

随机推荐

  1. CA Loves GCD (BC#78 1002) (hdu 5656)

    CA Loves GCD  Accepts: 135  Submissions: 586  Time Limit: 6000/3000 MS (Java/Others)  Memory Limit: ...

  2. SecureCRT使用sz和rz命令进行文件的上传和下载

    SecureCRT可以使用sz和rz命令进行文件的上传和下载. sz文件下载: 格式:sz 文件名称 即可将服务器的文件下载至本地. rz文件上传: 格式:rz 文件名称 即可将本地文件上传至服务器. ...

  3. (转)linux服务器安全配置攻略

    引言: 最小的权限+最少的服务=最大的安全 所以,无论是配置任何服务器,我们都必须把不用的服务关闭.把系统权限设置到最小话,这样才能保证服务器最大的安全.下面是CentOS服务器安全设置,供大家参考. ...

  4. 由360手机卫士谈起——让你的service获取最高权限。

    近日来,我在倒腾360手机卫士的时候,发现,你无论是把他数据清空,还是把它强行停止以后,甚至是把它卸载以后,它的service都没有被Android的系统干掉,依然是岿然不动了.我就感到了纳闷了,后来 ...

  5. TCP字节流和UDP数据报区别

    两者的区别在于TCP接收的是一堆数据,而每次取多少由主机决定;而UDP发的是数据报,客户发送多少就接收多少. 拥有这些区别的原因是由于TCP和UDP的特性不同而决定的.TCP是面向连接的,也就是说,在 ...

  6. Programming Entity Framework CodeFirst -- 约定和属性配置

     以下是EF中Data Annotation和 Fluenlt API的不同属性约定的对照.   Length Data Annotation MinLength(nn) MaxLength(nn) ...

  7. karma作为jQuery单元测试Runner

    karma作为angular测试runner出现,如果你使用过karma一定感受到这很不错的javascript测试runner.简单干净的配置文件karma.config.js,以及karma in ...

  8. IO完成端口

    从MSDN中翻译了IO完成端口的文章,不得不说翻译的很烂,英语需要继续提高啊... 在一个多处理器系统上,IO完成端口提供一个非常高效的线程模型来处理多个异步IO请求.当一个进程创建了一个IO完成端口 ...

  9. Sqlserver 如何获取每组中的第一条记录

    在日常生活方面,我们经常需要记录一些操作,类似于日志的操作,最后的记录才是有效数据,而且可能它们属于不同的方面.功能下面,从数据库的术语来说,就是查找出每组中的一条数据. 例子 我们要从上面获得的有效 ...

  10. [我给Unity官方视频教程做中文字幕]beginner Graphics – Lessons系列之灯光介绍Lights

    [我给Unity官方视频教程做中文字幕]beginner Graphics – Lessons系列之灯光介绍Lights 既上一篇分享了中文字幕的摄像机介绍Cameras后,本篇分享一下第2个已完工的 ...