接触就是表明物体间重叠的属性,或者说发生了碰撞。
概述定义:
接触是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留在作用域中
本文介绍了Box2D物理引擎中的接触概念,包括接触点、法线、分隔和群组。接触对象由引擎内部创建,开发者可以通过接触监听器和接触过滤器来获取和定制碰撞信息。接触监听器支持开始、结束、求解前和求解后四个事件,用于处理物体碰撞的各个阶段。接触过滤器允许开发者自定义碰撞筛选规则。

356

被折叠的 条评论
为什么被折叠?



