2011年2月13日 星期日

More Acceptance Tests

Box2d Testbed project 藏有許多漂亮的 demos,之前已經完成最簡單的 freefall 測試,這次玩複雜一點的 VaryingFriction 測試:準備五個箱子,摩擦係數皆不同。摩擦係數高 > 0,走的距離短,最後會卡在滑坡上;摩擦係數零的箱子會一直滑下去 (牛頓第一運動定律:動者恆動靜者恆靜)。

這次 test 採用 gtest 的 Test Fixtures,這樣就可以寫各式各樣類似的測試 (僅僅 time_step 會變)。

整個 code 很簡單:
#include <gtest/gtest.h>
#include "physics_simulation/box2d/box2d_service.h"
#include "physics_simulation/box2d/box2d_world.h"
#include "physics_simulation/box2d/box2d_body.h"

#include "physics_simulation/world_config.h"
#include "physics_simulation/body_config.h"
#include "physics_simulation/fixture_config.h"
#include "physics_simulation/polygon_shape_config.h"
#include <math.h>

namespace physics_simulation {
namespace box2d {

class VaryingFrictionTest : public testing::Test {
protected:
virtual void SetUp();
virtual void TearDown();

World* world_;
Body* box_[5];
Body* track_[3];
Body* ground_;

private:
void SetUpWorld();
void SetUpGround();
void SetUpTopTrack();
void SetUpMiddleTrack();
void SetUpBottomTrack();
void SetUpRightStopper();
void SetUpLeftStopper();
void SetUpFiveBoxes();

Body* stopper_[2];
};

void VaryingFrictionTest::SetUp() {
SetUpWorld();
SetUpGround();
SetUpTopTrack();
SetUpMiddleTrack();
SetUpBottomTrack();
SetUpRightStopper();
SetUpLeftStopper();
SetUpFiveBoxes();
}

void VaryingFrictionTest::TearDown() {
delete world_;
}

void VaryingFrictionTest::SetUpWorld() {
Service* service = new Box2dService();

WorldConfig config;
config.gravity.set(0.0f, -10.0f);
this->world_ = service->createWorld(config);

delete service;
}

void VaryingFrictionTest::SetUpGround() {
BodyConfig config;
this->ground_ = world_->createBody(config);

PolygonShapeConfig shape;
shape.setAsEdge(math::Vector2(-40.0f, 0.0f),
math::Vector2( 40.0f, 0.0f));
FixtureConfig fixture;
fixture.shape_config = &shape;
fixture.density = 0.0f;
this->ground_->createFixture(fixture);
}

void VaryingFrictionTest::SetUpTopTrack() {
BodyConfig config;
config.position.set(-4.0f, 22.0f);
config.angle = -0.25f;
this->track_[0] = world_->createBody(config);

PolygonShapeConfig shape;
shape.setAsBox(13.0f, 0.25f);
FixtureConfig fixture;
fixture.shape_config = &shape;
fixture.density = 0.0f;
this->track_[0]->createFixture(fixture);
}

void VaryingFrictionTest::SetUpMiddleTrack() {
BodyConfig config;
config.position.set(4.0f, 14.0f);
config.angle = 0.25f;
this->track_[1] = world_->createBody(config);

PolygonShapeConfig shape;
shape.setAsBox(13.0f, 0.25f);
FixtureConfig fixture;
fixture.shape_config = &shape;
fixture.density = 0.0f;
this->track_[1]->createFixture(fixture);
}

void VaryingFrictionTest::SetUpBottomTrack() {
BodyConfig config;
config.position.set(-4.0f, 6.0f);
config.angle = -0.25f;
this->track_[2] = world_->createBody(config);

PolygonShapeConfig shape;
shape.setAsBox(13.0f, 0.25f);
FixtureConfig fixture;
fixture.shape_config = &shape;
fixture.density = 0.0f;
this->track_[2]->createFixture(fixture);
}

void VaryingFrictionTest::SetUpRightStopper() {
BodyConfig config;
config.position.set(10.5f, 19.0f);
this->stopper_[0] = world_->createBody(config);

PolygonShapeConfig shape;
shape.setAsBox(0.25f, 1.0f);
FixtureConfig fixture;
fixture.shape_config = &shape;
fixture.density = 0.0f;
this->stopper_[0]->createFixture(fixture);
}

void VaryingFrictionTest::SetUpLeftStopper() {
BodyConfig config;
config.position.set(-10.5f, 11.0f);
this->stopper_[1] = world_->createBody(config);

PolygonShapeConfig shape;
shape.setAsBox(0.25f, 1.0f);
FixtureConfig fixture;
fixture.shape_config = &shape;
fixture.density = 0.0f;
this->stopper_[1]->createFixture(fixture);
}

void VaryingFrictionTest::SetUpFiveBoxes() {
PolygonShapeConfig shape;
shape.setAsBox(0.5f, 0.5f);

FixtureConfig fixture;
fixture.shape_config = &shape;
fixture.density = 25.0f;

const float friction[5] = { 0.75f, 0.5f, 0.35f, 0.1f, 0.0f };
for (int i = 0; i < 5; ++i) {
BodyConfig config;
config.type = BodyConfig::Dynamic;
config.position.set(-15.0f + 4.0f * i, 28.0f);
box_[i] = world_->createBody(config);

fixture.friction = friction[i];
box_[i]->createFixture(fixture);
}
}

namespace {

float dist(const Vector2& u, const Vector2& v) {
return Vector2(u.x - v.x, u.y - v.y).length();
}

} // namespace

TEST_F(VaryingFrictionTest, AfterOneMinute) {
const int steps = 60 * 60; // One minute
const float time_step = 1.0f/60.0f;
for (int i = 0; i < steps; ++i) {
world_->simulate(time_step);
}

// Box #0 and #1 are on the top track.
for (int i = 0; i < 2; ++i) {
EXPECT_LT(dist(box_[i]->position(), track_[0]->position()),
dist(box_[i]->position(), track_[1]->position()));
EXPECT_LT(dist(box_[i]->position(), track_[0]->position()),
dist(box_[i]->position(), track_[2]->position()));
}

// Box #2 is on the bottom track
EXPECT_LT(dist(box_[2]->position(), track_[2]->position()),
dist(box_[2]->position(), track_[1]->position()));
EXPECT_LT(dist(box_[2]->position(), track_[2]->position()),
dist(box_[2]->position(), track_[0]->position()));

// Box #3 is on the ground
EXPECT_FLOAT_EQ(0.51503909f, box_[3]->position().y);

// Box #4 is out of the screen
EXPECT_TRUE(box_[4]->position().length() > 2000.0f);
}

} // namespace box2d
} // namespace physics_simulation
強力推薦大家使用 Google C++ Testing Framework. 接下來再寫幾個 acceptance tests,然後開始偷竊游戲物理設定。

沒有留言:

張貼留言