geom/segment: add bresenham iterator

This commit is contained in:
Danny Robson 2019-03-22 15:11:01 +11:00
parent f6cec803d7
commit 382e093f57
2 changed files with 136 additions and 0 deletions

View File

@ -83,6 +83,97 @@ namespace cruft::geom {
} }
/// An implementation of Bresenham's line algorithm.
/// Ref: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
///
/// This operates as a container that iterates over the integral points
/// on the line. Use in range-for is explicitly supported.
///
/// The start and end segment points will both be generated.
class bresenham {
public:
bresenham (cruft::geom::segment<2,int> _src)
: m_src (_src)
{ ; }
class end_iterator { };
end_iterator end (void) { return {}; }
class begin_iterator {
public:
begin_iterator (cruft::geom::segment<2,int> _src)
: m_start (_src.a)
, m_diff (_src.b - _src.a)
{
// Find the axis directions and finalise the diff
auto const sign = select (m_diff > 0, 1, -1);
m_diff = abs (m_diff);
// Setup the increment for each possible major axis.
if (m_diff.x > m_diff.y) {
m_dirx = { sign.x, 0 };
m_diry = { 0, sign.y };
} else {
std::swap (m_diff.x, m_diff.y);
m_dirx = { 0, sign.y };
m_diry = { sign.x, 0 };
}
// Set the initial offsets
D = 2 * m_diff.y - m_diff.x;
x = y = 0;
}
begin_iterator& operator++ () &
{
// We've gone far enough. Drop down a line.
if (D >= 0) {
y += 1;
D -= 2 * m_diff.x;
}
// Step forwards.
D += 2 * m_diff.y;
++x;
return *this;
}
bool operator== (end_iterator const&) const noexcept
{
return x >= m_diff.x + 1;
}
bool operator!= (end_iterator const &rhs) const noexcept
{
return !(*this == rhs);
}
cruft::point2i operator* () const noexcept
{
return m_start + x * m_dirx + y * m_diry;
}
private:
cruft::point2i m_start; /// The initial point of the segment.
cruft::vector2i m_diff; /// Magnitude of total traversal for each axis.
cruft::vector2i m_dirx; /// Direction of increment for the X axis.
cruft::vector2i m_diry; /// Direction of increment for the Y axis.
int D; /// Stepping accumulator to detect line changes .
int x; /// Current X parameter.
int y; /// Current Y parameter.
};
begin_iterator begin (void) const { return { m_src }; }
private:
cruft::geom::segment<2,int> m_src;
};
using segment2i = segment<2,int>; using segment2i = segment<2,int>;
using segment3i = segment<3,int>; using segment3i = segment<3,int>;

View File

@ -69,6 +69,50 @@ void test_region_intersection (cruft::TAP::logger &tap)
} }
///////////////////////////////////////////////////////////////////////////////
void test_bresenham (cruft::TAP::logger &tap)
{
static struct {
cruft::point2i a;
cruft::point2i b;
std::vector<cruft::point2i> expected;
char const *message;
} const TESTS[] = {
{
.a = { 0 },
.b = { 3 },
.expected = { { 0 }, { 1 }, { 2 }, { 3 } },
.message = "positive unit direction"
},
{
.a = { 1, 2 },
.b = { 1, 4 },
.expected = { { 1, 2 }, { 1, 3 }, { 1, 4 } },
.message = "vertical only"
},
{
.a = { -2, 1 },
.b = { 1, -1 },
.expected = { { -2, 1 }, { -1, 0 }, { 0, 0 }, { 1, -1 } },
.message = "cross origin"
},
{
.a = { 1, 1 },
.b = { 1, 1 },
.expected = { { 1, 1 } },
.message = "zero length segment"
},
};
for (auto const &t: TESTS) {
std::vector<cruft::point2i> computed;
for (auto const &p: cruft::geom::bresenham ({ t.a, t.b }))
computed.push_back (p);
tap.expect_eq (computed, t.expected, "bresenham: %!", t.message);
}
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
int int
main (int, char**) main (int, char**)
@ -76,5 +120,6 @@ main (int, char**)
cruft::TAP::logger tap; cruft::TAP::logger tap;
test_point_distance (tap); test_point_distance (tap);
test_region_intersection (tap); test_region_intersection (tap);
test_bresenham (tap);
return tap.status (); return tap.status ();
} }