diff --git a/geom/segment.hpp b/geom/segment.hpp index 9dd8f860..06eb69fc 100644 --- a/geom/segment.hpp +++ b/geom/segment.hpp @@ -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 segment3i = segment<3,int>; diff --git a/test/geom/segment.cpp b/test/geom/segment.cpp index be11012a..aa45a4e9 100644 --- a/test/geom/segment.cpp +++ b/test/geom/segment.cpp @@ -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 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 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 main (int, char**) @@ -76,5 +120,6 @@ main (int, char**) cruft::TAP::logger tap; test_point_distance (tap); test_region_intersection (tap); + test_bresenham (tap); return tap.status (); } \ No newline at end of file