#ifndef MARCHINGSQUARES_HPP #define MARCHINGSQUARES_HPP #include #include #include #include #include namespace marchsq { // Marks a square in the grid struct Coord { long r = 0, c = 0; Coord() = default; explicit Coord(long s) : r(s), c(s) {} Coord(long _r, long _c): r(_r), c(_c) {} size_t seq(const Coord &res) const { return r * res.c + c; } Coord& operator+=(const Coord& b) { r += b.r; c += b.c; return *this; } Coord operator+(const Coord& b) const { Coord a = *this; a += b; return a; } }; // Closed ring of cell coordinates using Ring = std::vector; // Specialize this struct to register a raster type for the Marching squares alg template struct _RasterTraits { // The type of pixel cell in the raster using ValueType = typename T::ValueType; // Value at a given position static ValueType get(const T &raster, size_t row, size_t col); // Number of rows and cols of the raster static size_t rows(const T &raster); static size_t cols(const T &raster); }; // Specialize this to use parellel loops within the algorithm template struct _Loop { template static void for_each(It from, It to, Fn &&fn) { for (auto it = from; it < to; ++it) fn(*it, size_t(it - from)); } }; namespace __impl { template using RasterTraits = _RasterTraits>; template using TRasterValue = typename RasterTraits::ValueType; template size_t rows(const T &raster) { return RasterTraits::rows(raster); } template size_t cols(const T &raster) { return RasterTraits::cols(raster); } template TRasterValue isoval(const T &rst, const Coord &crd) { return RasterTraits::get(rst, crd.r, crd.c); } template void for_each(ExecutionPolicy&& policy, It from, It to, Fn &&fn) { _Loop::for_each(from, to, fn); } // Type of squares (tiles) depending on which vertices are inside an ROI // The vertices would be marked a, b, c, d in counter clockwise order from the // bottom left vertex of a square. // d --- c // | | // | | // a --- b enum class SquareTag : uint8_t { // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 none, a, b, ab, c, ac, bc, abc, d, ad, bd, abd, cd, acd, bcd, full }; template constexpr std::underlying_type_t _t(E e) noexcept { return static_cast>(e); } enum class Dir: uint8_t { left, down, right, up, none}; static const constexpr Dir NEXT_CCW[] = { /* 00 */ Dir::none, // SquareTag::none (empty square, nowhere to go) /* 01 */ Dir::left, // SquareTag::a /* 02 */ Dir::down, // SquareTag::b /* 03 */ Dir::left, // SquareTag::ab /* 04 */ Dir::right, // SquareTag::c /* 05 */ Dir::none, // SquareTag::ac (ambiguous case) /* 06 */ Dir::down, // SquareTag::bc /* 07 */ Dir::left, // SquareTag::abc /* 08 */ Dir::up, // SquareTag::d /* 09 */ Dir::up, // SquareTag::ad /* 10 */ Dir::none, // SquareTag::bd (ambiguous case) /* 11 */ Dir::up, // SquareTag::abd /* 12 */ Dir::right, // SquareTag::cd /* 13 */ Dir::right, // SquareTag::acd /* 14 */ Dir::down, // SquareTag::bcd /* 15 */ Dir::none // SquareTag::full (full covered, nowhere to go) }; static const constexpr uint8_t PREV_CCW[] = { /* 00 */ 1 << _t(Dir::none), /* 01 */ 1 << _t(Dir::up), /* 02 */ 1 << _t(Dir::left), /* 03 */ 1 << _t(Dir::left), /* 04 */ 1 << _t(Dir::down), /* 05 */ 1 << _t(Dir::up) | 1 << _t(Dir::down), /* 06 */ 1 << _t(Dir::down), /* 07 */ 1 << _t(Dir::down), /* 08 */ 1 << _t(Dir::right), /* 09 */ 1 << _t(Dir::up), /* 10 */ 1 << _t(Dir::left) | 1 << _t(Dir::right), /* 11 */ 1 << _t(Dir::left), /* 12 */ 1 << _t(Dir::right), /* 13 */ 1 << _t(Dir::up), /* 14 */ 1 << _t(Dir::right), /* 15 */ 1 << _t(Dir::none) }; const constexpr uint8_t DIRMASKS[] = { /*left: */ 0x01, /*down*/ 0x12, /*right */0x21, /*up*/ 0x10, /*none*/ 0x00 }; inline Coord step(const Coord &crd, Dir d) { uint8_t dd = DIRMASKS[uint8_t(d)]; return {crd.r - 1 + (dd & 0x0f), crd.c - 1 + (dd >> 4)}; } template class Grid { const Rst * m_rst = nullptr; Coord m_cellsize, m_res_1, m_window, m_gridsize, m_grid_1; std::vector m_tags; // Assign tags to each square Coord rastercoord(const Coord &crd) const { return {(crd.r - 1) * m_window.r, (crd.c - 1) * m_window.c}; } Coord bl(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, 0}; } Coord br(const Coord &crd) const { return tl(crd) + Coord{m_res_1.r, m_res_1.c}; } Coord tr(const Coord &crd) const { return tl(crd) + Coord{0, m_res_1.c}; } Coord tl(const Coord &crd) const { return rastercoord(crd); } bool is_within(const Coord &crd) { long R = rows(*m_rst), C = cols(*m_rst); return crd.r >= 0 && crd.r < R && crd.c >= 0 && crd.c < C; }; // Calculate the tag for a cell (or square). The cell coordinates mark the // top left vertex of a square in the raster. v is the isovalue uint8_t get_tag_for_cell(const Coord &cell, TRasterValue v) { Coord sqr[] = {bl(cell), br(cell), tr(cell), tl(cell)}; uint8_t t = ((is_within(sqr[0]) && isoval(*m_rst, sqr[0]) >= v)) + ((is_within(sqr[1]) && isoval(*m_rst, sqr[1]) >= v) << 1) + ((is_within(sqr[2]) && isoval(*m_rst, sqr[2]) >= v) << 2) + ((is_within(sqr[3]) && isoval(*m_rst, sqr[3]) >= v) << 3); assert(t < 16); return t; } // Get a cell coordinate from a sequential index Coord coord(size_t i) const { return {long(i) / m_gridsize.c, long(i) % m_gridsize.c}; } size_t seq(const Coord &crd) const { return crd.seq(m_gridsize); } bool is_visited(size_t idx, Dir d = Dir::none) const { SquareTag t = get_tag(idx); uint8_t ref = d == Dir::none ? PREV_CCW[_t(t)] : uint8_t(1 << _t(d)); return t == SquareTag::full || t == SquareTag::none || ((m_tags[idx] & 0xf0) >> 4) == ref; } void set_visited(size_t idx, Dir d = Dir::none) { m_tags[idx] |= (1 << (_t(d)) << 4); } bool is_ambiguous(size_t idx) const { SquareTag t = get_tag(idx); return t == SquareTag::ac || t == SquareTag::bd; } // Search for a new starting square size_t search_start_cell(size_t i = 0) const { // Skip ambiguous tags as starting tags due to unknown previous // direction. while ((i < m_tags.size()) && (is_visited(i) || is_ambiguous(i))) ++i; return i; } SquareTag get_tag(size_t idx) const { return SquareTag(m_tags[idx] & 0x0f); } Dir next_dir(Dir prev, SquareTag tag) const { // Treat ambiguous cases as two separate regions in one square. switch (tag) { case SquareTag::ac: switch (prev) { case Dir::down: return Dir::right; case Dir::up: return Dir::left; default: assert(false); return Dir::none; } case SquareTag::bd: switch (prev) { case Dir::right: return Dir::up; case Dir::left: return Dir::down; default: assert(false); return Dir::none; } default: return NEXT_CCW[uint8_t(tag)]; } return Dir::none; } struct CellIt { Coord crd; Dir dir= Dir::none; const Rst *grid = nullptr; TRasterValue operator*() const { return isoval(*grid, crd); } CellIt& operator++() { crd = step(crd, dir); return *this; } CellIt operator++(int) { CellIt it = *this; ++(*this); return it; } bool operator!=(const CellIt &it) { return crd.r != it.crd.r || crd.c != it.crd.c; } using value_type = TRasterValue; using pointer = TRasterValue *; using reference = TRasterValue &; using difference_type = long; using iterator_category = std::forward_iterator_tag; }; // Two cell iterators representing an edge of a square. This is then // used for binary search for the first active pixel on the edge. struct Edge { CellIt from, to; }; Edge _edge(const Coord &ringvertex) const { size_t idx = ringvertex.r; Coord cell = coord(idx); uint8_t tg = m_tags[ringvertex.r]; SquareTag t = SquareTag(tg & 0x0f); switch (t) { case SquareTag::a: case SquareTag::ab: case SquareTag::abc: return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; case SquareTag::b: case SquareTag::bc: case SquareTag::bcd: return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; case SquareTag::c: return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; case SquareTag::ac: switch (Dir(ringvertex.c)) { case Dir::left: return {{tl(cell), Dir::down, m_rst}, {bl(cell)}}; case Dir::right: return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; default: assert(false); } case SquareTag::d: case SquareTag::ad: case SquareTag::abd: return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; case SquareTag::bd: switch (Dir(ringvertex.c)) { case Dir::down: return {{bl(cell), Dir::right, m_rst}, {br(cell)}}; case Dir::up: return {{tr(cell), Dir::left, m_rst}, {tl(cell)}}; default: assert(false); } case SquareTag::cd: case SquareTag::acd: return {{br(cell), Dir::up, m_rst}, {tr(cell)}}; case SquareTag::full: case SquareTag::none: { Coord crd{tl(cell) + Coord{m_cellsize.r / 2, m_cellsize.c / 2}}; return {{crd, Dir::none, m_rst}, {crd}}; } } return {}; } Edge edge(const Coord &ringvertex) const { const long R = rows(*m_rst), C = cols(*m_rst); const long R_1 = R - 1, C_1 = C - 1; Edge e = _edge(ringvertex); e.to.dir = e.from.dir; ++e.to; e.from.crd.r = std::min(e.from.crd.r, R_1); e.from.crd.r = std::max(e.from.crd.r, 0l); e.from.crd.c = std::min(e.from.crd.c, C_1); e.from.crd.c = std::max(e.from.crd.c, 0l); e.to.crd.r = std::min(e.to.crd.r, R); e.to.crd.r = std::max(e.to.crd.r, 0l); e.to.crd.c = std::min(e.to.crd.c, C); e.to.crd.c = std::max(e.to.crd.c, 0l); return e; } public: explicit Grid(const Rst &rst, const Coord &cellsz, const Coord &overlap) : m_rst{&rst} , m_cellsize{cellsz} , m_res_1{m_cellsize.r - 1, m_cellsize.c - 1} , m_window{overlap.r < cellsz.r ? cellsz.r - overlap.r : cellsz.r, overlap.c < cellsz.c ? cellsz.c - overlap.c : cellsz.c} , m_gridsize{2 + (long(rows(rst)) - overlap.r) / m_window.r, 2 + (long(cols(rst)) - overlap.c) / m_window.c} , m_tags(m_gridsize.r * m_gridsize.c, 0) {} // Go through the cells and mark them with the appropriate tag. template void tag_grid(ExecutionPolicy &&policy, TRasterValue isoval) { // parallel for r for_each (std::forward(policy), m_tags.begin(), m_tags.end(), [this, isoval](uint8_t& tag, size_t idx) { tag = get_tag_for_cell(coord(idx), isoval); }); } // Scan for the rings on the tagged grid. Each ring vertex stores the // sequential index of the cell and the next direction (Dir). // This info can be used later to calculate the exact raster coordinate. std::vector scan_rings() { std::vector rings; size_t startidx = 0; while ((startidx = search_start_cell(startidx)) < m_tags.size()) { Ring ring; size_t idx = startidx; Dir prev = Dir::none, next = next_dir(prev, get_tag(idx)); while (next != Dir::none && !is_visited(idx, prev)) { Coord ringvertex{long(idx), long(next)}; ring.emplace_back(ringvertex); set_visited(idx, prev); idx = seq(step(coord(idx), next)); prev = next; next = next_dir(next, get_tag(idx)); } // To prevent infinite loops in case of degenerate input if (next == Dir::none) m_tags[startidx] = _t(SquareTag::none); if (ring.size() > 1) { ring.pop_back(); rings.emplace_back(ring); } } return rings; } // Calculate the exact raster position from the cells which store the // sequantial index of the square and the next direction template void interpolate_rings(ExecutionPolicy && policy, std::vector &rings, TRasterValue isov) { for_each(std::forward(policy), rings.begin(), rings.end(), [this, isov] (Ring &ring, size_t) { for (Coord &ringvertex : ring) { Edge e = edge(ringvertex); CellIt found = std::lower_bound(e.from, e.to, isov); ringvertex = found.crd; } }); } }; template std::vector execute_with_policy(ExecutionPolicy && policy, const Raster & raster, TRasterValue isoval, Coord windowsize = {}) { if (!rows(raster) || !cols(raster)) return {}; size_t ratio = cols(raster) / rows(raster); if (!windowsize.r) windowsize.r = 2; if (!windowsize.c) windowsize.c = std::max(2l, long(windowsize.r * ratio)); Coord overlap{1}; Grid grid{raster, windowsize, overlap}; grid.tag_grid(std::forward(policy), isoval); std::vector rings = grid.scan_rings(); grid.interpolate_rings(std::forward(policy), rings, isoval); return rings; } template std::vector execute(const Raster &raster, TRasterValue isoval, Coord windowsize = {}) { return execute_with_policy(nullptr, raster, isoval, windowsize); } } // namespace __impl using __impl::execute_with_policy; using __impl::execute; } // namespace marchsq #endif // MARCHINGSQUARES_HPP