`
rocky225
  • 浏览: 11410 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
社区版块
存档分类
最新评论

2D多边形碰撞检测和反馈

 
阅读更多

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:扩展应用于碰撞反馈的分离轴的方法。
文章3:对快速移动的对象来检测他的碰撞。
文章4:基本的Arcade碰撞反馈
文章5:处理旋转
文章6:计算碰撞点
文章7:刚体动力学。
分离轴的方法:
这是碰撞检测的核心,原理是非常简单的,也非常的容易实现。它也是快速和稳定的,因为没有除法在运算中被使用。我将会带给大家一个简单的两个BOX碰撞测试的例子。


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


现在,我们已经有一个检测他们是否碰撞的法则了。这些代码也能够被扩展为3D。
所有的分隔轴都需要被测试
计算的是每一个多边形所在的轴的间隔(分离面的法线)
测试这些间隔是否交叉。

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


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

很明显了,如果对象A是静态的,例如是环境的一部分,整个MTD都应用到了B(B.position-=MTD)。
如何来处理快速移动的对象
以上的方法只能处理慢速的对象,但是当对象快速的移动的时候,碰撞会不准确,忽略掉一些碰撞,甚至是允许对象互相穿越,这是一种糟糕的情况。

我们可以再次的使用分离轴的方法,扩展到他的将来,使用这一规则来在未来的某个时间来计算碰撞,原理是一样的,以下的这个图片就是一个很好的解释:


http://blog.csdn.net/sunyeyi/article/details/2125685

分享到:
评论

相关推荐

    box2d碰撞检测教程

    Box2D是一款强大的开源物理引擎,常用于2D游戏开发,提供精确的物理模拟和碰撞检测。Cocos2D是一个流行的2D游戏开发框架,它支持多种编程语言,包括C++、Objective-C和Python等。将Box2D与Cocos2d结合使用,可以为...

    Cocos2d-x碰撞检测原理与英雄要打死怪物--之游戏开发《赵云要格斗》

    Box2D提供了一个强大的多边形碰撞检测系统,可以创建具有不同形状(如圆形、多边形)的物理体,并通过模拟真实世界的物理规则来检测碰撞。在Cocos2d-x中,你可以通过集成Box2D并创建`CCPhysicsWorld`,然后为每个...

    物理碰撞检测实例

    在游戏开发中,物理碰撞检测是一项至关重要的技术,它使得游戏中的对象能够相互...通过对“physicsContact”文件的分析和实践,我们可以更好地理解和掌握如何在cocos2d-x中实现高效的物理碰撞检测,从而提升游戏体验。

    cocos2d-x游戏开发系列教程-坦克大战游戏之所有坦克之间的碰撞检测

    cocos2d-x提供了一些内置的几何形状类,如`CCRect`(矩形)和`CCPolygon`(多边形),用于表示游戏对象的边界,并提供了相应的碰撞检测方法。对于坦克这类基本为矩形的物体,我们可以使用`CGRectIntersectsRect()`...

    cocos2d-x游戏开发系列教程-坦克大战游戏之子弹的碰撞检测处理

    在cocos2d-x中,可以为每个游戏对象创建一个物理身体,设置其形状(如圆形、多边形等),并开启物理引擎进行实时碰撞检测。当发生碰撞时,会触发相应的物理碰撞事件。 在处理子弹与坦克的碰撞时,我们需要创建一个...

    VelcroPhysics:具有逼真的物理响应的高性能2D碰撞检测系统

    连续碰撞检测(带冲击求解器的时间) 联系人回调:开始,结束,预解决,后解决凸,凹多边形和圆形。 每个身体有多种形状动态树和四叉树宽相快速的AABB广相查询和射线广播碰撞组和类别睡眠管理摩擦与赔偿线性时间...

    Box2D-2.3.0用户手册-中文版

    Box2D 是一种开放源代码的物理引擎,用于模拟2D刚体和碰撞检测。下面是根据 Box2D 2.3.0 用户手册-中文版生成的知识点总结: Chapter 1 导言 1.1 关于 Box2D * Box2D 是一种物理引擎,用于模拟2D刚体和碰撞检测...

    JBox2d-2.0.1.zip

    3. **传感器与碰撞检测**:JBox2D提供了碰撞检测功能,可以检测两个刚体是否发生接触,并通过传感器触发特定的事件,这对于游戏中的交互逻辑至关重要。 4. **世界管理**:JBox2D使用一个全局的“世界”对象来管理...

    box2d 2.3.1文档

    - 合理设置刚体的类别和掩码位,以减少不必要的碰撞检测。 - 减少世界中的刚体数量和复杂度。 **3.2 调试与测试** - 利用调试绘制功能来可视化物理世界。 - 创建单元测试来验证特定物理场景的行为。 **3.3 高级...

    Box2D v2.3.0 用户手册

    碰撞检测是物理仿真中非常关键的部分,负责处理物体之间的相互作用和碰撞响应。 力学模块则涵盖了物体的定义和工厂,物体包括静态体、运动体和动力体,它们分别对应着在Box2D世界中不会移动、只能以速度移动和受到...

    jbox2D.zip

    jBox2D保持了Box2D的核心功能,包括刚体、关节、碰撞检测、摩擦力和浮力等,使得开发者可以轻松地模拟出物体的运动、碰撞、弹跳等现象,为游戏带来真实的物理反馈。 二、Android环境下的jBox2D集成 在Android项目...

    简单的2d图形系统

    通常,这可以通过设置鼠标光标状态、检测鼠标点击事件并与图形的边界进行碰撞检测来实现。一旦图形被选中,其位置可以随着鼠标的移动而改变,这就需要用到坐标转换和矩阵运算。 4. **旋转操作**:围绕任意点旋转...

    xhitv3 cocos2d ios

    在这个游戏中,开发者可能利用Cocos2d-iOS的动画和用户交互功能,创建出游戏界面和操作方式,同时利用Box2D来处理游戏中的物理效果,如角色的移动、碰撞后的反馈等。这样的组合使得游戏不仅有视觉上的吸引力,还具备...

    类别20:碰撞检测算法

    2. `Box2D.js`:源自C++的Box2D物理引擎的JavaScript版本,包含精确的碰撞检测和响应。 3. `collide.js`:轻量级的碰撞检测库,专注于简单几何形状的碰撞。 4. `Detect.js`:提供多种基础形状的碰撞检测,如圆形、...

    Box2d应用—愤怒的小鸟

    优化物体的碰撞检测算法,可以提高游戏运行效率;而增强用户反馈,如小鸟发射后的轨迹预测,可以使游戏更具趣味性和挑战性。 在实际项目中,开发者通常会创建自定义的Box2D世界,设置重力方向和强度,然后将游戏...

    Box2D的超级子集合

    Box2D是一个开源的二维物理引擎,广泛用于游戏和模拟领域,它能够处理刚体动力学、碰撞检测、碰撞响应等物理模拟。LiquidFun是Box2D的一个扩展,它专注于模拟流体、颗粒系统等更为复杂的物理效果,为物理世界增加了...

    Box2D v2.1.0 中文版文档 pdf版

    2. **碰撞模块(CollisionModule)**:负责处理物体间的碰撞检测和响应。 3. **动态模块(DynamicsModule)**:模拟物体的动力学行为,如加速度、速度等。 4. **夹具(Fixtures)**:介绍如何创建和管理夹具。 5. **...

    山寨版愤怒小鸟工程(box2d)

    Box2D提供了精确的碰撞检测算法,确保了游戏的逻辑正确性。当碰撞发生时,引擎会自动处理碰撞后的反弹、摩擦力等效果,让游戏体验更加逼真。 此外,开发者还会用到Box2D的关节和约束。例如,弹簧关节可以让猪堡的...

    碰撞检测算法

    JavaScript社区提供了许多成熟的碰撞检测库,如`Box2D.js`、`Matter.js`和`p2.js`等,它们封装了各种复杂的碰撞检测算法,开发者可以方便地集成到项目中,快速实现碰撞功能。 六、实际应用 碰撞检测算法在...

    应用源码之(圆形碰撞).zip

    在这个项目中,开发者可能探讨了如何在2D空间内实现物体之间的碰撞检测,这对于游戏开发或者任何涉及动态图形交互的应用来说都是关键技术。 首先,我们要理解Android中的图形绘制基础。Android提供了Canvas类,允许...

Global site tag (gtag.js) - Google Analytics