We left off in Episode 5 trying to avoid Dr. Bob hitting us with a rolled-up newspaper as we forgot to account for the front tiles’s z position. Let’s see if we can fix this. First, we change the test for the front model origin to expect a `WorldPt3` which, obviously, does contain a z-component.

TEST_N(FrontModelOrigin)
{
SimpleCabinetModel simpleCabinet(600, 720, 800);
FrontModel frontModel(simpleCabinet.GetFrontModelOrigin());
CHECK_EQUAL(WorldPt3(SimpleCabinetModel::kBoardThickness, 0, SimpleCabinetModel::kBoardThickness), frontModel.GetOrigin());
}

Making this test pass requires a series of simple changes to `FrontModel`.

class FrontModel {
public:
FrontModel(const WorldPt3& origin) :
fOrigin(origin) {
}
WorldPt3 GetOrigin() const {
return fOrigin;
}
private:
WorldPt3 fOrigin;
}

After making these changes gcc tells us that we should probably fix the tests `FrontModelPositionOfFirstSegment` and `FrontModelPositionOfSecondSegment` in order to make this thing compilable again. As in `FrontModelOrigin`, this requires us to replace `WorldPt` with `WorldPt3` and we’re basically done.

With this out of the way, let’s concentrate on generalizing our `FrontModel`. So far, we’re able to calculate the position of the first and second segment / tile (assuming both fit into the cabinet). Looking at the screenshot in episode 0, it sure looks like the front tiles should be arranged in rows & columns. Let’s see if we can work towards this. How about computing the number of front tile rows & columns which “fit” into a given cabinet? Let’s start with a simple test – if the cabinet is too small, no front tiles will fit at all.

TEST_N(FrontModelNoRowsNoColumnsNotWideEnough)
{
SimpleCabinetModel simpleCabinet(150, 720, 800);
FrontModel frontModel(simpleCabinet.GetFrontModelOrigin(),
simpleCabinet.GetFrontWidth(),
simpleCabinet.GetFrontHeight());
CHECK_EQUAL(0, frontModel.GetNumRows());
CHECK_EQUAL(0, frontModel.GetNumColumns());
}

In order to be able to compute the number of rows & columns, we have to supply the `FrontModel` with the space available for placing front tiles. So, we need tests to for these functions. Let’s `#if 0` the last test for a moment and write new tests for `GetFrontWidth()` and `GetFrontHeight()`.

TEST_N(SimpleCabinetFrontWidthHeight)
{
SimpleCabinetModel simpleCabinet(600, 720, 800);
CHECK_EQUAL(600 - 2 * SimpleCabinetModel::kBoardThickness,
simpleCabinet.GetFrontWidth());
CHECK_EQUAL(720 - 2 * SimpleCabinetModel::kBoardThickness,
simpleCabinet.GetFrontHeight());
}

Let’s make this test pass.

class SimpleCabinetModel {
public:
WorldCoord GetFrontWidth() const {
return fWidth - 2 * kBoardThickness;
}
WorldCoord GetFrontHeight() const {
return fHeight - 2 * kBoardThickness;
}
};

Backtracking to our test `FrontModelNoRowsNoColumnsNotWideEnough`, we change `FrontModel`‘s constructor (again).

class FrontModel {
public:
FrontModel(const WorldPt3& origin, WorldCoord width, WorldCoord height) :
fOrigin(origin), fWidth(width), fHeight(height) {
}
};

What was our intital goal? Ah, computing the number of rows & columns (in order to make the test pass). Well, that’s straightforward:

class FrontModel {
public:
long GetNumRows() const {
return 0;
}
long GetNumColumns() const {
return 0;
}
};

Indeed, it’s pretty straightforward for the test we’re trying to make pass. We just write enough code to make the test pass. And returning `0` makes the test pass. Let’s move on to the next test.

TEST_N(FrontModelThreeRowsTwoColumnsFits)
{
SimpleCabinetModel simpleCabinet(2 * 200 + 2 * SimpleCabinetModel::kBoardThickness,
3 * 150 + 2 * SimpleCabinetModel::kBoardThickness,
800);
FrontModel frontModel(simpleCabinet.GetFrontModelOrigin(),
simpleCabinet.GetFrontWidth(),
simpleCabinet.GetFrontHeight());
CHECK_EQUAL(3, frontModel.GetNumRows());
CHECK_EQUAL(2, frontModel.GetNumColumns());
}

Now is the right time to flesh out `GetNumRows()` and `GetNumColumns()`.

class FrontModel {
public:
long GetNumRows() const {
return MyMathUtils::fdiv(fHeight, SimpleCabinetModel::kSegmentHeight);
}
long GetNumColumns() const {
return MyMathUtils::fdiv(fWidth, SimpleCabinetModel::kSegmentWidth);
}
};

In order to get the number of front tiles fitting into a row, we need to divide the overall width available for the front tiles and divide it (as in division of integers) by the width of a segment. Same for the number of front tiles fitting into a column.

Where are we at? We now have code for calculating the number of tiles in x direction (columns) and z direction (rows). If we would have a function computing the x, y & z position of a front tile at a given row & column we could certainly beef up `SimpleCabinetCreator::CreateFrom()` with a nice loop placing all the front tiles. Let’s write a test for this function computing a tiles position:

TEST_N(FrontModelPositionOfARowColumn)
{
SimpleCabinetModel simpleCabinet(1200, 720, 800);
FrontModel frontModel(simpleCabinet.GetFrontModelOrigin(),
simpleCabinet.GetFrontWidth(),
simpleCabinet.GetFrontHeight());
WorldPt3 expectedPosition = frontModel.GetOrigin() +
WorldPt3(3 * SimpleCabinetModel::kSegmentWidth, 0,
2 * SimpleCabinetModel::kSegmentHeight)
CHECK_EQUAL(expectedPosition,
frontModel.GetSegmentPosition(3, 2));
}

Here’s the code to make the test pass:

class FrontModel {
public:
WorldPt3 GetSegmentPosition(long column, long row) const
{
WorldPt3 position(GetOrigin());
position += WorldPt3(column * fSegmentWidth,
0,
row * fSegmentHeight);
return position;
}
};

And for the grand final of this episode, let’s tackle placing the front tile symbols in `SimpleCabinetCreator::CreateFrom()`. With the preparatory work we’ve done in this episode it should be pretty straightforward – and it is:

void CreateFrom(const FrontModel& frontModel) {
for (long i = 0; i frontModel.GetNumColumns(); i++)
for (long j = 0; j < frontModel.GetNumRows(); j++) {
VWObject frontSegment = gSDK->PlaceSymbolByNameN(frontModel.GetFrontSymbolName(), WorldPt(0, 0));
frontSegment.MoveObject3D(frontModel.GetSegmentPosition(i, j));
fSimpleCabinet.AddObject(frontSegment);
}
}

Tune in next week when you hear Nurse Piggy say: “Are we done yet? I have a date with a certain frog for dinner… “.

Previous Episode | Next Episode