Skip to content

Commit

Permalink
#5382: Add row- and column-wise iterators which can traverse the matr…
Browse files Browse the repository at this point in the history
…ix major in reverse direction
  • Loading branch information
codereader committed Dec 8, 2020
1 parent 8fb5189 commit 7a4291f
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 50 deletions.
114 changes: 88 additions & 26 deletions libs/patch/PatchIterators.h
Expand Up @@ -195,76 +195,138 @@ class SinglePatchColumnReverseIterator :

// An iterator traversing a given patch column-wise, iterating over
// one column after the other (which are optionally constrained to [startColumn..endColumn])
class ColumnWisePatchIterator :
// in the given row direction (+1 == Forward, -1 == Backwards)
class ColumnWisePatchIteratorBase :
public PatchControlIterator
{
public:
ColumnWisePatchIterator(IPatch& patch) :
ColumnWisePatchIterator(patch, 0, patch.getWidth() - 1)
{
assert(patch.getWidth() > 0);
}
ColumnWisePatchIteratorBase(IPatch& patch, int rowDelta) :
ColumnWisePatchIteratorBase(patch, 0, patch.getWidth() - 1, rowDelta)
{}

ColumnWisePatchIterator(IPatch& patch, std::size_t startColumn, std::size_t endColumn) :
PatchControlIterator(patch, 0, startColumn,
std::bind(ColumnWisePatchIterator::moveNext, std::placeholders::_1, std::ref(patch), endColumn))
ColumnWisePatchIteratorBase(IPatch& patch, std::size_t startColumn, std::size_t endColumn, int rowDelta) :
PatchControlIterator(patch, rowDelta > 0 ? 0 : patch.getHeight() - 1, startColumn,
std::bind(ColumnWisePatchIteratorBase::moveNext, std::placeholders::_1, std::ref(patch), endColumn, rowDelta))
{}

private:
static void moveNext(PatchControlIterator& it, const IPatch& patch, std::size_t endColumn)
static void moveNext(PatchControlIterator& it, const IPatch& patch, std::size_t endColumn, int rowDelta)
{
auto nextRow = it.getRow() + 1;
auto nextRow = it.getRow() + rowDelta;
auto nextColumn = it.getColumn();

if (nextRow >= patch.getHeight())
if (rowDelta < 0 && nextRow < 0 ||
rowDelta > 0 && nextRow >= patch.getHeight())
{
// Advance to the next column
// If that doesn't succeed, just leave the indices out of bounds
if (++nextColumn <= endColumn)
{
nextRow = 0;
nextRow = rowDelta > 0 ? 0 : patch.getHeight() - 1;
}
}

it.set(nextRow, nextColumn);
}
};

// An iterator traversing a given patch column-wise, iterating over
// one column after the other (which are optionally constrained to [startColumn..endColumn])
class ColumnWisePatchIterator :
public ColumnWisePatchIteratorBase
{
public:
ColumnWisePatchIterator(IPatch& patch) :
ColumnWisePatchIteratorBase(patch, +1)
{}

ColumnWisePatchIterator(IPatch& patch, std::size_t startColumn, std::size_t endColumn) :
ColumnWisePatchIteratorBase(patch, startColumn, endColumn, +1)
{}
};

// An iterator traversing a given patch column-wise, iterating over
// one column after the other (which are optionally constrained to [startColumn..endColumn])
// with each column traversed backwards, row=[height-1...0]
class ColumnWisePatchReverseIterator :
public ColumnWisePatchIteratorBase
{
public:
ColumnWisePatchReverseIterator(IPatch& patch) :
ColumnWisePatchIteratorBase(patch, -1)
{}

ColumnWisePatchReverseIterator(IPatch& patch, std::size_t startColumn, std::size_t endColumn) :
ColumnWisePatchIteratorBase(patch, startColumn, endColumn, -1)
{}
};

// An iterator traversing a given patch row-wise, iterating over
// one row after the other (which are optionally constrained to [startRow..endRow])
class RowWisePatchIterator :
class RowWisePatchIteratorBase :
public PatchControlIterator
{
public:
RowWisePatchIterator(IPatch& patch) :
RowWisePatchIterator(patch, 0, patch.getHeight() - 1)
{
assert(patch.getHeight() > 0);
}
RowWisePatchIteratorBase(IPatch& patch, int columnDelta) :
RowWisePatchIteratorBase(patch, 0, patch.getHeight() - 1, columnDelta)
{}

RowWisePatchIterator(IPatch& patch, std::size_t startRow, std::size_t endRow) :
PatchControlIterator(patch, startRow, 0,
std::bind(RowWisePatchIterator::moveNext, std::placeholders::_1, std::ref(patch), endRow))
RowWisePatchIteratorBase(IPatch& patch, std::size_t startRow, std::size_t endRow, int columnDelta) :
PatchControlIterator(patch, startRow, columnDelta > 0 ? 0 : patch.getWidth() - 1,
std::bind(RowWisePatchIteratorBase::moveNext, std::placeholders::_1, std::ref(patch), endRow, columnDelta))
{}

private:
static void moveNext(PatchControlIterator& it, const IPatch& patch, std::size_t endRow)
static void moveNext(PatchControlIterator& it, const IPatch& patch, std::size_t endRow, int columnDelta)
{
auto nextColumn = it.getColumn() + 1;
auto nextColumn = it.getColumn() + columnDelta;
auto nextRow = it.getRow();

if (nextColumn >= patch.getWidth())
if (columnDelta > 0 && nextColumn >= patch.getWidth() ||
columnDelta < 0 && nextColumn < 0)
{
// Advance to the next row
// If that doesn't succeed, just leave the indices out of bounds
if (++nextRow <= endRow)
{
nextColumn = 0;
nextColumn = columnDelta > 0 ? 0 : patch.getWidth() - 1;
}
}

it.set(nextRow, nextColumn);
}
};

// An iterator traversing a given patch row-wise, iterating over
// one row after the other (which are optionally constrained to [startRow..endRow])
// columns are traversed in reverse order, col=[width-1...0]
class RowWisePatchIterator :
public RowWisePatchIteratorBase
{
public:
RowWisePatchIterator(IPatch& patch) :
RowWisePatchIteratorBase(patch, +1)
{}

RowWisePatchIterator(IPatch& patch, std::size_t startRow, std::size_t endRow) :
RowWisePatchIteratorBase(patch, startRow, endRow, +1)
{}
};

// An iterator traversing a given patch row-wise, iterating over
// one row after the other (which are optionally constrained to [startRow..endRow])
// columns are traversed in reverse order, col=[width-1...0]
class RowWisePatchReverseIterator :
public RowWisePatchIteratorBase
{
public:
RowWisePatchReverseIterator(IPatch& patch) :
RowWisePatchIteratorBase(patch, -1)
{}

RowWisePatchReverseIterator(IPatch& patch, std::size_t startRow, std::size_t endRow) :
RowWisePatchIteratorBase(patch, startRow, endRow, -1)
{}
};

}
156 changes: 132 additions & 24 deletions test/PatchIterators.cpp
Expand Up @@ -69,6 +69,32 @@ TEST_F(PatchIteratorTest, IterateOverWholePatchColumnWise)
EXPECT_EQ(expected, expectedValues.end()); // assume no underflow
}

TEST_F(PatchIteratorTest, IterateOverWholePatchColumnWiseRowBackwards)
{
auto patch = createWorldspawnPatch(3, 5);

std::vector<Vector3> expectedValues;

// Fill the vector with the expected values
for (auto col = 0; col < patch->getPatch().getWidth(); ++col)
{
for (auto row = static_cast<int>(patch->getPatch().getHeight()) -1; row >= 0; --row)
{
expectedValues.push_back(patch->getPatch().ctrlAt(row, col).vertex);
}
}

patch::ColumnWisePatchReverseIterator it(patch->getPatch());
auto expected = expectedValues.begin();

while (it.isValid())
{
EXPECT_EQ((it++)->vertex, *(expected++));
}

EXPECT_EQ(expected, expectedValues.end()); // assume no underflow
}

TEST_F(PatchIteratorTest, IterateOverWholePatchRowWise)
{
auto patch = createWorldspawnPatch(3, 5);
Expand All @@ -95,20 +121,59 @@ TEST_F(PatchIteratorTest, IterateOverWholePatchRowWise)
EXPECT_EQ(expected, expectedValues.end()); // assume no underflow
}

void iterateOverPartialPatchColumnWise(IPatch& patch, std::size_t startCol, std::size_t endCol)
TEST_F(PatchIteratorTest, IterateOverWholePatchRowWiseColumnBackwards)
{
auto patch = createWorldspawnPatch(3, 5);

std::vector<Vector3> expectedValues;

// Fill the vector with the expected values
for (auto row = 0; row < patch->getPatch().getHeight(); ++row)
{
for (auto col = static_cast<int>(patch->getPatch().getWidth()) - 1; col >= 0; --col)
{
expectedValues.push_back(patch->getPatch().ctrlAt(row, col).vertex);
}
}

patch::RowWisePatchReverseIterator it(patch->getPatch());
auto expected = expectedValues.begin();

while (it.isValid())
{
EXPECT_EQ((it++)->vertex, *(expected++));
}

EXPECT_EQ(expected, expectedValues.end()); // assume no underflow
}

void iterateOverPartialPatchColumnWise(IPatch& patch, std::size_t startCol, std::size_t endCol, bool rowBackwards)
{
std::vector<Vector3> expectedValues;

// Fill the vector with the expected values
for (auto col = startCol; col <= endCol; ++col)
{
for (auto row = 0; row < patch.getHeight(); ++row)
if (rowBackwards)
{
for (auto row = static_cast<int>(patch.getHeight()) - 1; row >= 0; --row)
{
expectedValues.push_back(patch.ctrlAt(row, col).vertex);
}
}
else
{
expectedValues.push_back(patch.ctrlAt(row, col).vertex);
for (auto row = 0; row < patch.getHeight(); ++row)
{
expectedValues.push_back(patch.ctrlAt(row, col).vertex);
}
}
}

patch::ColumnWisePatchIterator it(patch, startCol, endCol);
auto it = !rowBackwards ?
static_cast<patch::PatchControlIterator>(patch::ColumnWisePatchIterator(patch, startCol, endCol)) :
patch::ColumnWisePatchReverseIterator(patch, startCol, endCol);

auto expected = expectedValues.begin();

while (it.isValid())
Expand All @@ -124,30 +189,58 @@ TEST_F(PatchIteratorTest, IterateOverPartialPatchColumnWise)
auto patch = createWorldspawnPatch(5, 7);

// Try various start and end column configs
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 4);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 3);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 0);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 4);
iterateOverPartialPatchColumnWise(patch->getPatch(), 3, 4);
iterateOverPartialPatchColumnWise(patch->getPatch(), 1, 3);
iterateOverPartialPatchColumnWise(patch->getPatch(), 4, 4);
iterateOverPartialPatchColumnWise(patch->getPatch(), 3, 4);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 4, false);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 3, false);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 0, false);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 4, false);
iterateOverPartialPatchColumnWise(patch->getPatch(), 3, 4, false);
iterateOverPartialPatchColumnWise(patch->getPatch(), 1, 3, false);
iterateOverPartialPatchColumnWise(patch->getPatch(), 4, 4, false);
iterateOverPartialPatchColumnWise(patch->getPatch(), 3, 4, false);
}

TEST_F(PatchIteratorTest, IterateOverPartialPatchColumnWiseRowBackwards)
{
auto patch = createWorldspawnPatch(5, 7);

// Try various start and end column configs
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 4, true);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 3, true);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 0, true);
iterateOverPartialPatchColumnWise(patch->getPatch(), 0, 4, true);
iterateOverPartialPatchColumnWise(patch->getPatch(), 3, 4, true);
iterateOverPartialPatchColumnWise(patch->getPatch(), 1, 3, true);
iterateOverPartialPatchColumnWise(patch->getPatch(), 4, 4, true);
iterateOverPartialPatchColumnWise(patch->getPatch(), 3, 4, true);
}

void iterateOverPartialPatchRowWise(IPatch& patch, std::size_t startRow, std::size_t endRow)
void iterateOverPartialPatchRowWise(IPatch& patch, std::size_t startRow, std::size_t endRow, bool columnBackwards)
{
std::vector<Vector3> expectedValues;

// Fill the vector with the expected values
for (auto row = startRow; row <= endRow; ++row)
{
for (auto col = 0; col < patch.getWidth(); ++col)
if (columnBackwards)
{
for (auto col = static_cast<int>(patch.getWidth()) - 1; col >= 0; --col)
{
expectedValues.push_back(patch.ctrlAt(row, col).vertex);
}
}
else
{
expectedValues.push_back(patch.ctrlAt(row, col).vertex);
for (auto col = 0; col < patch.getWidth(); ++col)
{
expectedValues.push_back(patch.ctrlAt(row, col).vertex);
}
}
}

patch::RowWisePatchIterator it(patch, startRow, endRow);
auto it = !columnBackwards ?
static_cast<patch::PatchControlIterator>(patch::RowWisePatchIterator(patch, startRow, endRow)) :
patch::RowWisePatchReverseIterator(patch, startRow, endRow);

auto expected = expectedValues.begin();

while (it.isValid())
Expand All @@ -163,14 +256,29 @@ TEST_F(PatchIteratorTest, IterateOverPartialPatchRowWise)
auto patch = createWorldspawnPatch(5, 7);

// Try various start and end column configs
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 6);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 3);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 0);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 4);
iterateOverPartialPatchRowWise(patch->getPatch(), 3, 6);
iterateOverPartialPatchRowWise(patch->getPatch(), 1, 3);
iterateOverPartialPatchRowWise(patch->getPatch(), 6, 6);
iterateOverPartialPatchRowWise(patch->getPatch(), 3, 4);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 6, false);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 3, false);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 0, false);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 4, false);
iterateOverPartialPatchRowWise(patch->getPatch(), 3, 6, false);
iterateOverPartialPatchRowWise(patch->getPatch(), 1, 3, false);
iterateOverPartialPatchRowWise(patch->getPatch(), 6, 6, false);
iterateOverPartialPatchRowWise(patch->getPatch(), 3, 4, false);
}

TEST_F(PatchIteratorTest, IterateOverPartialPatchRowWiseColumnBackwards)
{
auto patch = createWorldspawnPatch(5, 7);

// Try various start and end column configs
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 6, true);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 3, true);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 0, true);
iterateOverPartialPatchRowWise(patch->getPatch(), 0, 4, true);
iterateOverPartialPatchRowWise(patch->getPatch(), 3, 6, true);
iterateOverPartialPatchRowWise(patch->getPatch(), 1, 3, true);
iterateOverPartialPatchRowWise(patch->getPatch(), 6, 6, true);
iterateOverPartialPatchRowWise(patch->getPatch(), 3, 4, true);
}

void iterateOverSingleColum(IPatch& patch, std::size_t colToTest)
Expand Down

0 comments on commit 7a4291f

Please sign in to comment.