cocos2dx物理引擎和碰撞检测(八)接触(Contants)

本文介绍了Box2D物理引擎中的接触概念,包括接触点、法线、分隔和群组。接触对象由引擎内部创建,开发者可以通过接触监听器和接触过滤器来获取和定制碰撞信息。接触监听器支持开始、结束、求解前和求解后四个事件,用于处理物体碰撞的各个阶段。接触过滤器允许开发者自定义碰撞筛选规则。

接触就是表明物体间重叠的属性,或者说发生了碰撞。

概述定义:
接触是Box2D物理引擎创建用于管理框架间碰撞的对象。接触也具有很多的类型。它们都派生自基类b2Contant。之所以有类似的划分,是为了用于管理不同类型形状之间的接触。例如有管理多边形碰撞的类,也有管理圆形碰撞的类。下面先来熟悉一些与接触有关的术语:
1.接触点(contant point)

接触点就是两个形状相互接触的点。在引擎中近似的认为接触区是由少数的点组成的。

2.接触法线(contant normal)

接触法线是一个单位向量,用来标明两个框架接触时的方向。它是由一个框架对象指向另一个的。按照引擎中的惯例,向量由fixtureA指向fixtureB。

3.接触分隔(contant separation)

接触分隔正好与穿透(重叠)区域相反。当形状相互接触时,会有一段重叠的区域,而分隔就是与其相反的。因此接触分隔的数值为负。

4.接触群组(contant manifold)

考虑一下圆和圆,圆和多边形的碰撞,只会得到一个接触点和向量。两个凸变形相互接触,有可能产生两个接触点。这些点都有相同的法线,将它们分成一组构成了接触群组。

Box2D引擎选择了一种方式,引擎当中会先创建接触来分析碰撞,如果形状之间没有发生碰撞,则会正确的删除接触,否则可能一直等到物体不再重叠,再删除接触点。

接触类(Contant Class)

接触对象是由引擎内部创建和摧毁的,并不是由开发者来创建的。开发者只要得到物理模拟后的结果。然而,我们还是能够访问接触类并和它交互的。开发者可以通过下述函数来访问原始的接触群组。

b2Manifold *GetManifold();

const b2Manifold *GetManifold()const;


上述函数返回的接触点信息,是基于物体本身坐标的。如果开发者需要将其转换为物理世界中的坐标系,则可以使用下面函数:

void GetWorldManifold(b2WorldManifold *worldManifold)const;

这使用了物体的当前位置去计算接触点在world坐标下的位置。还有一点需要注意,对于传感器Sensors,引擎并不会创建接触群组,如果开发者想要获得碰撞的信息,则要使用下面的方式:

bool touching = sensorContant->IsTouching();


 

另外,这个函数对非传感器的其他框架对象也是有效果的。从接触对象中,开发者可以得到发生接触的框架对象,从而得到物体对象:

b2Fixture *fixtureA = myContact->GetFixtureA();

b2Body *bodyA = fixtureA->GetBody();

MyActor *actorA =(MyActor *)bodyA->GetUserData();

访问接触:

接触对象包含了一系列与物体碰撞有关的信息。作为开发者该如何访问这些信息呢?Box2D引擎提供了几种函数访问的方法:为了访问接触,首先可以直接查询物理世界或者物体结构中的接触对象:

//遍历所有的接触对象

for(b2Contant *c =myWorld->GetContantList();c;c=c->GetNext())

{

    //process C;

}                         
//遍历物体中的所有接触对象

for(b2ContantEdge *ce = myBody->GetContantList();ce;ce=ce->GetNext())

{

   b2Contact *c = ce->contact;

  //process C;

}


注意:通过b2World或者b2Body直接访问,有可能会错过一些时间步产生的临时接触。而使用b2ContactListener就可以很精确的得到全部结果。

接触监听器:
通过实现接触监听器的对象,开发者就可以收到接触数据。接触监听器支持四种事件:开始(begin),结束(end),求解前(pre-slove)和求解后(post-slove)。它们表示了物体碰撞的过程,代码如下:

class MyContactListener:public b2ContactListener

{

public:

       //开始接触

      void BeginContact(b2Contact *contact);

       //结束接触

       void EndContact(b2Contact *contact);

       //求解之前的

       void PreSolve(b2Contact *contact,const b2Manifold *oldManifold);

       //求解之后的

      void PostSolve(b2Contact *contact,const b2Manifold *oldManifold);

};


在物理世界运行的过程中,读者可以创建监听器的实力对象,并使用函数b2World::SetContactListener来注册这个对象。但要保证当世界对象存在时,监听器要留在作用域中。

BeginContact函数:当两个框架开始重叠时,触发函数,这个过程只能在时间步发生,也就是说会在b2World::step函数执行的过程中调用。

EndContact函数:当两个函数不再重叠时或一个物体被摧毁时,就会触发结束的事件,这个事件不仅会发生在时间步之内,也有可能发生时间步之外。

PreSolve函数:在碰撞检测之后,但在碰撞求解(物理模拟)之前,此函数事件触发。这样是为了给开发者一个机会,根据当前情况来决定是否使这个接触失败。在回调的函数中使用一个设置函数就可以实现单侧碰撞的功能。每次碰撞碰撞处理时,接触会重新生效,开发者不得不在每一个时间步中都设置同一个接触无效。由于连续碰撞检测,preslove时间在单个时间步可能发生多次,代码如下:

void PreSolve(b2Contact* contact,const b2Manifolf *oldManifold)

{

//获得接触群组

       b2WorldManifold worldManifold;

       contact->GetWorldManifold(&worldManifold);

       if(worldManifold.normal.y<-0.5f){

       //设置此接触无效

       contact->SetEnabled(false);

       }

}



PostSolve函数:当引擎完成了碰撞求解,也就是物理模拟过程之后,开发者可以得到碰撞冲量的结果,调用此函数。这将是提供给开发者附加碰撞结果的机会。

在一个接触回调函数PostSolve中去改变物理世界的模拟数据,是一件神神奇的工作。例如,在游戏中可能会以碰撞的方式来消灭敌人。此时,在游戏中可能会以碰撞的方式来消灭敌人。此时开发者可以在此函数施加伤害,并试图摧毁关联的角色和它的刚体。这样的话可能会摧毁引擎正在运算的对象,造成访问错误。如果想处理这样的情况,就需要缓冲所有游戏中的关注的接触对象,然后再时间步之后处理它们。因为在时间步之后,物理模拟和碰撞检测的过程暂时停止。此时就应该立即处理它们,否则等到下一个时间步,物理世界就会发生改变。那么缓冲的对象也会失效。

下面来一段代码延时了再错做接触缓冲时如何处理那些物体,代码中假定了所有触点都缓冲于b2ContactPoint数组m_points中:

//打算摧毁和contact有所关联的物体指针

//必须先缓存那些需要摧毁的物体,因为它们有可能被多个触点所共有

const int32 k_maxNuke = 6;

//将要摧毁的物体数组

b2Body* nuke[k_maxNuke];

int32 nukeCount = 0;

//遍历contact缓存,摧毁相互接触时无重复的物体

for(int32 i =0; i<m_pointCount;++i)

{

      ContactPoint* point = m_points[i];

      //得到物体

      b2Body* body1 = point->shape1->GetBody();

      b2Body* body2 = point->shape2->GetBody();

      //得到物体的质量

      float32 mass1 = body1->GetMass();

      flost32 mass2 = body2->GetMass();

      //存储物体

       if(mass1>0.0f && mass2>0.0f){

          if(mass2 > mass1){

                nuke[nukeCount++]=body1;

           }

           else{

                nuke[nukeCount++]=body2;

            }

            if(nukeCount == k_maxNuke){break;}

       }

}

//将nuke数组排序,使得重复的指针归在一起

std::sort(nuke,nuke+nukeCount);

//删除body,跳过重置的

int32 i=0;

while(i < nukeCount)

{

    b2Body *b =nuke[i++];

    while(i<nukeCount && nuke[i]==b){

         ++i;  

    }

     m_world->DestroyBody(b);

}


接触筛选:

开发者可以通过实现类b2ContactFilter对象就可以倒到筛选接触的目的。引擎是允许开发者定制接触筛选内容的。通过实现一个函数ShouleCollide的代码,开发者可以自由地定义碰撞筛选的规则。这个函数会传递两个形状对象b2Shape的指针作为参数。如果发生碰撞返回true,否则为false。

//默认筛选函数

bool b2ContactFilter::shouldCollide(b2Shape *shape1,b2Shape *shape2)

{

    //得到筛选的数值

    const b2FilterData & filter1 = shape 1->GetFilterData();

    const b2FilterData & filter2 = shape 2->GetFilterData();

    //判断是否符合规则

    if(filter1.groupIndex == filter2.groupIndex && filter.groupIndex !=0){

      return  filter1.groupIndex>0;

    }

     bool collide = (filter1.maskBits & filter2.categoryBits)!=0 && (filter2.maskBits & filter1.categoryBits)!=0;

    return collide;//返回是否发生碰撞

}


 

在物理世界运行期间,开发者可以创建自定义的接触筛选案例,不过要保证筛选器对象要保留在作用域中,下面是代码:

MyContactFilter filter;

world->SetContactFilter(&filter);//确保filter留在作用域中


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值