Box2D v2.1.0用户手册翻译 - 第09章 接触(Contacts)

内容很多摘自
Aman JIANG(江超宇)翻译的Box2D v2.0.1 用户手册

第09章 接触(Contacts)

9.1 关于

接触(contact)是由 Box2D 创建的用于管理fixture间碰撞的对象。接触有不同的种类,它们都派生自 b2Contact,用于管理不同类型形状之间的接触。例如, 有管理多边形之间碰撞的类,有管理圆形之间碰撞的类。

这是与接触有关的术语

接触点(contact point)

接触点就两个形状相互接触的点。在Box2D中,近似地认为在少数点处有接触。

接触法线(contact normal)

接触法线是一个单位向量,由一个fixture指向另一个fixture。按照惯例,向量由fixtureA指向fixtureB。

接触分隔(contact separation)

分隔正好与穿透(penetration)相反。当形状相重叠时, 分隔为负。有可能将来的Box2D版本中会以正隔离来创建触点, 所以当有触点的报告时你可能需要检查一下符号。

contact manifold

两个凸多边形相互接触,有可能会产生两个接触点。这些点都有相同的法线,所以就相它们分成一组,构成contact manifold,这是连续区域接触的一个惯例。

(译注: manifold不知道怎么翻译。我猜测manifold是指有相同特性的东西归成一类。接触点有相同的法线,就归成contact manifold。)

法向冲量(normal impulse)

法向力作用于接触点,用于防止形状相互穿透。为方便起见,Box2D使用冲量(impulses)。法向力与时间步相乘,构成法向冲量

切向冲量(tangent impulse)

切向力会在接触点生成,用于模拟摩擦。为方便起见,切向作用使用冲量的方式存储。

接触标识(contact ids)

从最开始的猜测值出发,Box2d得出当前时间步的触点压力,再重新利用当前时间步的压力结果去推测下一个时间步的压力结果。接触标识用于匹配跨越时间步的触点。标识包含了几何特征索引以便区分触点。

当两个fixture的AABB重叠时,接触就被创建了。有时碰撞筛选会阻止接触的创建。当AABB 不再重叠后接触会被摧毁。

也许你会皱起眉头,为了没有发生实际碰撞的形状(只是它们的 AABB)却创建了接触。好吧,的确是这样的,这是一个“鸡或蛋”的问题。我们并不知道是否需要一个接触,除非我们创建一个接触去分析碰撞。如果形状之间没有发生碰撞,我们需要正确地删除接触,或者,我们可以一直等到 AABB 不再重 叠。为了提高性能, Box2D选择了后面这个方法。

9.2 接触类(Contact Class)

之前已经提及过,接触对象是Box2D内部创建和摧毁的,并不是由用户来创建。然而,你还是能够访问接触类并和它交互的。

你可以访问原始的contact manifold:

b2Manifold* GetManifold();

const b2Manifold* GetManifold()const;

你甚至可以修改manifold,一般情况下不提倡你怎样做。修改manifold是较高级的用法。

这个是帮助函数,去获取b2WorldManifold:

void GetWorldManifold(b2WorldManifold*worldManifold) const;

这使用了物体的当前位置去计算出接触点在world坐标下的位置。

传感器(Sensors)并不创建manifolds,所以要使用:

bool touching =sensorContact->IsTouching();

这函数对于非传感器(non-sensors)也有效。

从接触(contact)中你可以得到fixture, 从而再得到body。

b2Fixture* fixtureA = myContact->GetFixtureA();

b2Body* bodyA =fixtureA->GetBody();

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

你可以使一个接触失效。这仅仅在b2ContactListener::PreSolve事件中有效,下面会再进行讨论。

9.3 访问接触(Accessing Contacts)

你有几种方法来访问接触。为了访问接触,你可以直接查询world或者body结构,还可以实现一个接触监听器(contact listener)。

在world中,你可以遍历所有的接触:

for (b2Contact* c =myWorld->GetContactList(); c; c = c->GetNext())

{

    // process c

}

同样在body中,你也可以遍历所有接触。接触以图的方式存储,使用了接触边数据结构(contact edge structure),

for (b2ContactEdge* ce =myBody->GetContactList(); ce; ce = ce->next)

{

    b2Contact* c = ce->contact;

    // process c

}

通过下面描述的接触监听器,你也可以访问接触。

注意

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

9.4 接触监听器(Contact Listener)

通过实现 b2ContactListener 你就可以收到接触数据。接触监听器支持几种事件: 开始(begin),结束(end), 求解前(pre-solve), 求解后(post-solve)。

class MyContactListener : publicb2ContactListener

{

public:

    void BeginContact(b2Contact*contact)

    { // handle begin event }

 

    void EndContact(b2Contact* contact)

    { // handle end event }

 

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

    { // handle pre-solve event }

 

    void PostSolve(b2Contact* contact,const b2ContactImpulse* impulse)

    { // handle post-solve event }

};

 

注意

不要保存发送到b2ContactListener的指针。取而代之,用深拷贝的方式将触点数据保存到你自己的缓冲区。下面的例子演示了一种方法。

在运行期(run-time), 你可以创建listener的实例对象,并使用b2World::SetContactListener来注册这个对象。 但要保证当world对象存在时,listener要留在作用域中。

Begin事件

当两个fixture开始有重叠时,事件会被触发。传感器和非传感器都会触发这事件。这事件只能在时间步内(译注: 也就是b2World::step函数内部)发生。

End事件

当两个fixture不再重叠时,事件会被触发。传感器和非传感器都会触发这事件。当一个body被摧毁时,事件也有可能被触发。所以这事件也有可能发生在时间步之外。

Pre-Solve事件

在碰撞检测之后,但在碰撞求解之前,事件会被触发。这样可以给你一个机会,根据当前情况来决定是否使这个接触失效。 举个例子,在回调中使用b2Contact::SetEnabled(false),你就可以实现单侧碰撞的功能。每次碰撞处理时,接触会重新生效,所以你在每一个时间步 中都应禁用那个接触。由于连续碰撞检测,pre-solve事件在单个时间步中有可能发生多次。

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

{

    b2WorldManifold worldManifold;

    contact->GetWorldManifold(&worldManifold);

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

    {

       contact->SetEnabled(false);

    }

}

如果要确认触点状态或得到碰撞速度,可以在pre-solve事件中处理。

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

{

    b2WorldManifold worldManifold;

    contact->GetWorldManifold(&worldManifold);

    b2PointState state1[2], state2[2];

    b2GetPointStates(state1, state2,oldManifold, contact->GetManifold());

    if (state2[0] == b2_addState)

    {

        const b2Body* bodyA =contact->GetFixtureA()->GetBody();

        const b2Body* bodyB =contact->GetFixtureB()->GetBody();

        b2Vec2 point =worldManifold.points[0];

        b2Vec2 vA = bodyA->GetLinearVelocityFromWorldPoint(point);

        b2Vec2 vB =bodyB->GetLinearVelocityFromWorldPoint(point);

        float32 approachVelocity =b2Dot(vB – vA, worldManifold.normal);

        if (approachVelocity > 1.0f)

        {

            MyPlayCollisionSound();

        }

    }

}

 

Post-Solve事件

当你可以得到碰撞冲量(collision impulse)的结果时,post-solve事件会发生。 如果你不关心冲量,你可能只需要实现pre-solve事件。

在一个接触回调中去改变物理世界是诱人的。例如,你可能会以碰撞来施加伤害,并试图摧毁关联的角色和它的刚体。然而,Box2D并不允许你在回调中改变物理世界,因为你可能会摧毁 Box2D 正在运算的对象, 造成野指针。

处理触点的推荐方法是缓冲所有你关心的触点,并在时间步之后处理它们。一般在时间步之后你应该立即处理它们,否则其它客户端代码可能会改变物理世界,使你的缓冲失效。当你处理触点缓冲的时候,你可以去改变物理世界,但是你仍然应该小心不要造成无效的指针。在 testbed中有安全处理触点以避免无效指针的例子。

这是一小段 CollisionProcessing 测试中的代码,它演示了在操作触点缓冲时如何处理孤立物体。请注意注释。代码假定所有触点都缓冲于 b2ContactPoint 数组 m_points 中。

 

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

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

const int32 k_maxNuke = 6;

b2Body* nuke[k_maxNuke];

int32 nukeCount = 0;

 

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

for (int32 i = 0; i

{

    ContactPoint* point = m_points[i]

 

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

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

    float32 mass1 =body1->GetMass();

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

}

 

9.5 接触筛选(Contact Filtering)

通常,你不希望游戏中的所有物体都发生碰撞。例如,你可能会创建一个只有特定角色才能通过的门。 这称之为接触筛选,因为一些交互被筛选出了。

通过实现b2ContactFilter类, Box2D允许定制接触筛选。这个类需要你实现一个ShouldCollide 函数, 这个函数接收两个b2Shape的指针作为参数。如果应该碰撞那么就返回true。

默认的ShouldCollide实现使用了“第06章,夹具(Fixtures)”定义的b2FilterData。

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

{

    const b2FilterData& filter1 =shape1->GetFilterData();

    const b2FilterData& filter2 =shape2->GetFilterData();

 

    if (filter1.groupIndex ==filter2.groupIndex &&

        filter1.groupIndex != 0)

    {

        return filter1.groupIndex> 0;

    }

 

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

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

    return collide;

}

在运行期(run-time), 你可以创建自己的接触筛选实例,并使用b2World::SetContactFilter函数来注册。 你要保证当world存在时,你的filter要保留在作用域中。

MyContactFilter filter;

world->SetContactFilter(&filter);

// filter留在作用域中

 


你可能感兴趣的:(Box2D,v2.1.0用户手册翻译)