geom/segment: add bresenham iterator
This commit is contained in:
parent
f6cec803d7
commit
382e093f57
@ -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>;
|
||||||
|
|
||||||
|
@ -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 ();
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user