跳至正文

(转载)BOX2D V2.3.0 用户手册中文版(第10章)-世界类

Chapter 10 世界类

关于

b2World类包含物体和关节。它管理着模拟的方方面面,并允许异步查询(例如AABB查询和光线投射)。 你与Box2D的大部分交互都将通过 b2World 对象来完成。

创建和摧毁世界

创建world十分的简单。你只需提供一个重力矢量,和一个布尔量去指定物体是否可以休眠。 通常你会使用new和delete去创建和摧毁一个world。

b2World* myWorld = new b2World(gravity, doSleep);
... do stuff ...
delete myWorld;

使用世界

world类含有用于创建和摧毁物体与关节的工厂函数,,这些工厂函数已在物体和关节的章节中讨论过。 在这里我们讨论b2World的其它交互。

模拟

世界类用于驱动模拟。你需要指定一个时间步和一个速度及位置的迭代次数。例如:

float32 timeStep = 1.0f / 60.f;
int32 velocityIterations = 10;
int32 positionIterations = 8;
myWorld->Step(timeStep, velocityIterations, positionIterations);

在时间步完成之后,你可以调查物体和关节的信息。最经常的情况是你会获取物体的位置,这样你才能更新你的角色并渲染它们。你可以在游戏循环的任何地方执行时间步,但你应该注意事情发生的先后顺序。例如,如果你想要在一帧(frame)中得到新物体的碰撞结果,你必须在时间步之前创建物体。
正如之前我在 HelloWorld 教程中说明的,你需要使用一个固定的时间步。使用大一些的时间步你可以在低帧率的情况下提升性能。但通常情况下你应该使用一个不大于1/30秒的时间步。1/60 的时间步通常会呈现一个高质量的模拟。
迭代次数控制了约束求解器会遍历多少次世界中的接触以及关节。更多的迭代总能产生更好的模拟,但不要使用小频率大迭代数。60Hz和10次迭代远好于30Hz和20次迭代。
时间步之后,你应该清除任何施加到物体之上的力。使用b2World::ClearForces命令可以完成。这会让你在多个子步中使用相同的力。

myWorld->ClearForces();

探测世界

世界是物体、接触和关节的容器。你可以获取世界中所有物体、接触和关节并遍历它们。例如,这段代码会唤醒世界中的所有物体:

for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
    b->SetAwake(true);
}

不幸的是,真实的程序可能很复杂。例如,下面的代码是有错误的:

for (b2Body* b = myWorld->GetBodyList(); b; b = b->GetNext())
{
    GameActor* myActor = (GameActor*)b->GetUserData();
    if (myActor->IsDead())
    {
        myWorld->DestroyBody(b); // ERROR: now GetNext returns garbage.
    }
}

在物体摧毁之前一切都很顺利。一旦物体摧毁了, 它的next指针就变得非法。所以 b2Body::GetNext() 就会返回无用信息。 解决方法是在物体摧毁之前拷贝next指针。

b2Body* node = myWorld->GetBodyList();
while (node)
{
    b2Body* b = node;
    node = node->GetNext();

    GameActor* myActor = (GameActor*)b->GetUserData();
    if (myActor->IsDead())
    {
        myWorld->DestroyBody(b);
    }
}

这能安全地摧毁当前物体。然而,你可能想要调用一个游戏的函数来摧毁多个物体,这时你需要十分小心。 解决方案取决于具体应用,但为求方便,在此我给出一种解决这问题的方法。

b2Body* node = myWorld->GetBodyList();
while (node)
{
    b2Body* b = node;
    node = node->GetNext();

    GameActor* myActor = (GameActor*)b->GetUserData();
    if (myActor->IsDead())
    {
        bool otherBodiesDestroyed = GameCrazyBodyDestroyer(b);
        if (otherBodiesDestroyed)
        {
            node = myWorld->GetBodyList();
        }
    }
}

很明显要保证这个能正确工作, GameCrazyBodyDestroyer对它都摧毁了什么必须要诚实。

AABB查询

有时你需要得出一个区域内的所有fixture。b2World类为此使用了broad-phase数据结构,并提供了一个 log(N) 的快速方法。你提供一个世界坐标的AABB和b2QueryCallback的一个实现。只要fixture的AABB和需查询的AABB有重 合,world类就会调用你的b2QueryCallback类。返回true表示要继续查询,否则就返回false。例如,下面的代码找到所有大致与指 定AABB相交的fixtures并唤醒所有关联的物体。

class MyQueryCallback : public b2QueryCallback
{
public:
    bool ReportFixture(b2Fixture* fixture)
    {
        b2Body* body = fixture->GetBody();
        body->SetAwake(true);

        // Return true to continue the query.
        return true;
    }
};

...

MyQueryCallback callback;
b2AABB aabb;
aabb.lowerBound.Set(-1.0f, -1.0f);
aabb.upperBound.Set(1.0f, 1.0f);
myWorld->Query(&callback, aabb);

你不能假定回调函数会以固定的顺序执行。

光线投射

你可以使用光线投射去做现场(line-of-site)检查,开枪扫射等等。通过实现一个回调类,并提供一个开始点和结束点,你就可以执行光线投射。只要fixture被光线穿过,world就会调用你提供的类。回调时会传递fixture,交点,单位法向量,和光线通过的分数距离(fractional distance along the ray)。你不能假定回调会以固定的顺序执行。
通过返回fraction, 你可以控制光线投射是否继续执行。返回的fraction为0,表示应该结束光线投射。fraction为1,表示投射应该继续执行,并且没有和其它形状 相交。如果你返回参数列表中传进来的fraction,表示光线会被裁剪到当前的和形状的相交点。这样通过返回适当的fraction值,你可以投射任何形状,投射所有形状,或者只投射最接近的形状。
另外你可以返回fraction为-1,去过滤fixture。这样光线投射会继续执行,并表现得似乎fixture根本就存在。
这里是个例子:

// This class captures the closest hit shape.
class MyRayCastCallback : public b2RayCastCallback
{
public:
    MyRayCastCallback()
    {
        m_fixture = NULL;
    }

    float32 ReportFixture(b2Fixture* fixture, const b2Vec2& point,
                        const b2Vec2& normal, float32 fraction)
    {
        m_fixture = fixture;
        m_point = point;
        m_normal = normal;
        m_fraction = fraction;
        return fraction;
    }

    b2Fixture* m_fixture;
    b2Vec2 m_point;
    b2Vec2 m_normal;
    float32 m_fraction;
};

MyRayCastCallback callback;
b2Vec2 point1(-1.0f, 0.0f);
b2Vec2 point2(3.0f, 1.0f);
myWorld->RayCast(&callback, point1, point2);

注意
由于舍入误差,光线投射可能会通过在静态环境中的多边形之间的细小裂缝。如果这不是您的应用程序中的可接受的,请稍微扩大您的多边形。

void SetLinearVelocity(const b2Vec2& v);
b2Vec2 GetLinearVelocity() const;
void SetAngularVelocity(float32 omega);
float32 GetAngularVelocity() const;

力与冲量

你可以将力、扭矩和冲量应用到物体上。当应用一个力或者冲量时,你需要提供一个在世界坐标下的受力点。这经常导致相对于质心,会有个扭矩。

void ApplyForce(const b2Vec2& force, const b2Vec2& point);
void ApplyTorque(float32 torque);
void ApplyLinearImpulse(const b2Vec2& impulse, const b2Vec2& point);
void ApplyAngularImpulse(float32 impulse);

应用力、扭矩或冲量会唤醒物体。有时这是不合需求的。例如,你可能想要应用一个固定的力,并允许物体休眠来提升性能。这时,你可以使用这样的代码:

if (myBody->IsAwake() == true)
{
    myBody->ApplyForce(myForce, myPoint);
}

坐标转换

body类包含一些工具函数,它们可以帮助你在局部和世界坐标系之间转换点和向量。如果你不了解这些概念,请看 Jim Van Verth 和 Lars Bishop 的“游戏和交互应用的数学基础(Essential Mathematics for Games and Interactive Applications)”。这些函数都很高效(当内联时)。

b2Vec2 GetWorldPoint(const b2Vec2& localPoint);
b2Vec2 GetWorldVector(const b2Vec2& localVector);
b2Vec2 GetLocalPoint(const b2Vec2& worldPoint);
b2Vec2 GetLocalVector(const b2Vec2& worldVector);

列表

你可以遍历一个物体的fixture, 主要用途是帮助你访问fixture中的用户数据。

for (b2Fixture* f = body->GetFixtureList(); f; f = f->GetNext())
{
    MyFixtureData* data = (MyFixtureData*)f->GetUserData();
    ... do something with data ...
}

你也可以用类似的方法遍历物体的关节列表。
body也提供了访问相关contact的列表。你可以用来得到当前contact的信息。但使用时请小心,因为前一个时间步存在的contact,可能并不包含在当前列表中。

标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注