aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/contourpy/src/base_impl.h
diff options
context:
space:
mode:
authorshumkovnd <shumkovnd@yandex-team.com>2023-11-10 14:39:34 +0300
committershumkovnd <shumkovnd@yandex-team.com>2023-11-10 16:42:24 +0300
commit77eb2d3fdcec5c978c64e025ced2764c57c00285 (patch)
treec51edb0748ca8d4a08d7c7323312c27ba1a8b79a /contrib/python/contourpy/src/base_impl.h
parentdd6d20cadb65582270ac23f4b3b14ae189704b9d (diff)
downloadydb-77eb2d3fdcec5c978c64e025ced2764c57c00285.tar.gz
KIKIMR-19287: add task_stats_drawing script
Diffstat (limited to 'contrib/python/contourpy/src/base_impl.h')
-rw-r--r--contrib/python/contourpy/src/base_impl.h2454
1 files changed, 2454 insertions, 0 deletions
diff --git a/contrib/python/contourpy/src/base_impl.h b/contrib/python/contourpy/src/base_impl.h
new file mode 100644
index 0000000000..61b9c46132
--- /dev/null
+++ b/contrib/python/contourpy/src/base_impl.h
@@ -0,0 +1,2454 @@
+#ifndef CONTOURPY_BASE_IMPL_H
+#define CONTOURPY_BASE_IMPL_H
+
+#include "base.h"
+#include "converter.h"
+#include <iostream>
+
+namespace contourpy {
+
+// Point indices from current quad index.
+#define POINT_NE (quad)
+#define POINT_NW (quad-1)
+#define POINT_SE (quad-_nx)
+#define POINT_SW (quad-_nx-1)
+
+
+// CacheItem masks, only accessed directly to set. To read, use accessors detailed below.
+// 1 and 2 refer to level indices (lower and upper).
+#define MASK_Z_LEVEL_1 (0x1 << 0) // z > lower_level.
+#define MASK_Z_LEVEL_2 (0x1 << 1) // z > upper_level.
+#define MASK_Z_LEVEL (MASK_Z_LEVEL_1 | MASK_Z_LEVEL_2)
+#define MASK_MIDDLE_Z_LEVEL_1 (0x1 << 2) // middle z > lower_level
+#define MASK_MIDDLE_Z_LEVEL_2 (0x1 << 3) // middle z > upper_level
+#define MASK_MIDDLE (MASK_MIDDLE_Z_LEVEL_1 | MASK_MIDDLE_Z_LEVEL_2)
+#define MASK_BOUNDARY_E (0x1 << 4) // E edge of quad is a boundary.
+#define MASK_BOUNDARY_N (0x1 << 5) // N edge of quad is a boundary.
+// EXISTS_QUAD bit is always used, but the 4 EXISTS_CORNER are only used if _corner_mask is true.
+// Only one of EXISTS_QUAD or EXISTS_??_CORNER is ever set per quad.
+#define MASK_EXISTS_QUAD (0x1 << 6) // All of quad exists (is not masked).
+#define MASK_EXISTS_NE_CORNER (0x1 << 7) // NE corner exists, SW corner is masked.
+#define MASK_EXISTS_NW_CORNER (0x1 << 8)
+#define MASK_EXISTS_SE_CORNER (0x1 << 9)
+#define MASK_EXISTS_SW_CORNER (0x1 << 10)
+#define MASK_EXISTS_ANY_CORNER (MASK_EXISTS_NE_CORNER | MASK_EXISTS_NW_CORNER | MASK_EXISTS_SE_CORNER | MASK_EXISTS_SW_CORNER)
+#define MASK_EXISTS_ANY (MASK_EXISTS_QUAD | MASK_EXISTS_ANY_CORNER)
+#define MASK_START_E (0x1 << 11) // E to N, filled and lines.
+#define MASK_START_N (0x1 << 12) // N to E, filled and lines.
+#define MASK_START_BOUNDARY_E (0x1 << 13) // Lines only.
+#define MASK_START_BOUNDARY_N (0x1 << 14) // Lines only.
+#define MASK_START_BOUNDARY_S (0x1 << 15) // Filled and lines.
+#define MASK_START_BOUNDARY_W (0x1 << 16) // Filled and lines.
+#define MASK_START_CORNER (0x1 << 18) // Filled and lines.
+#define MASK_START_HOLE_N (0x1 << 17) // N boundary of EXISTS, E to W, filled only.
+#define MASK_ANY_START (MASK_START_N | MASK_START_E | MASK_START_BOUNDARY_N | MASK_START_BOUNDARY_E | MASK_START_BOUNDARY_S | MASK_START_BOUNDARY_W | MASK_START_HOLE_N | MASK_START_CORNER)
+#define MASK_LOOK_N (0x1 << 19)
+#define MASK_LOOK_S (0x1 << 20)
+#define MASK_NO_STARTS_IN_ROW (0x1 << 21)
+#define MASK_NO_MORE_STARTS (0x1 << 22)
+
+// Accessors for various CacheItem masks.
+#define Z_LEVEL(quad) (_cache[quad] & MASK_Z_LEVEL)
+#define Z_NE Z_LEVEL(POINT_NE)
+#define Z_NW Z_LEVEL(POINT_NW)
+#define Z_SE Z_LEVEL(POINT_SE)
+#define Z_SW Z_LEVEL(POINT_SW)
+#define MIDDLE_Z_LEVEL(quad) ((_cache[quad] & MASK_MIDDLE) >> 2)
+#define BOUNDARY_E(quad) (_cache[quad] & MASK_BOUNDARY_E)
+#define BOUNDARY_N(quad) (_cache[quad] & MASK_BOUNDARY_N)
+#define BOUNDARY_S(quad) (_cache[quad-_nx] & MASK_BOUNDARY_N)
+#define BOUNDARY_W(quad) (_cache[quad-1] & MASK_BOUNDARY_E)
+#define EXISTS_QUAD(quad) (_cache[quad] & MASK_EXISTS_QUAD)
+#define EXISTS_NE_CORNER(quad) (_cache[quad] & MASK_EXISTS_NE_CORNER)
+#define EXISTS_NW_CORNER(quad) (_cache[quad] & MASK_EXISTS_NW_CORNER)
+#define EXISTS_SE_CORNER(quad) (_cache[quad] & MASK_EXISTS_SE_CORNER)
+#define EXISTS_SW_CORNER(quad) (_cache[quad] & MASK_EXISTS_SW_CORNER)
+#define EXISTS_ANY(quad) (_cache[quad] & MASK_EXISTS_ANY)
+#define EXISTS_ANY_CORNER(quad) (_cache[quad] & MASK_EXISTS_ANY_CORNER)
+#define EXISTS_E_EDGE(quad) (_cache[quad] & (MASK_EXISTS_QUAD | MASK_EXISTS_NE_CORNER | MASK_EXISTS_SE_CORNER))
+#define EXISTS_N_EDGE(quad) (_cache[quad] & (MASK_EXISTS_QUAD | MASK_EXISTS_NW_CORNER | MASK_EXISTS_NE_CORNER))
+#define EXISTS_S_EDGE(quad) (_cache[quad] & (MASK_EXISTS_QUAD | MASK_EXISTS_SW_CORNER | MASK_EXISTS_SE_CORNER))
+#define EXISTS_W_EDGE(quad) (_cache[quad] & (MASK_EXISTS_QUAD | MASK_EXISTS_NW_CORNER | MASK_EXISTS_SW_CORNER))
+// Note that EXISTS_NE_CORNER(quad) is equivalent to BOUNDARY_SW(quad), etc.
+#define START_E(quad) (_cache[quad] & MASK_START_E)
+#define START_N(quad) (_cache[quad] & MASK_START_N)
+#define START_BOUNDARY_E(quad) (_cache[quad] & MASK_START_BOUNDARY_E)
+#define START_BOUNDARY_N(quad) (_cache[quad] & MASK_START_BOUNDARY_N)
+#define START_BOUNDARY_S(quad) (_cache[quad] & MASK_START_BOUNDARY_S)
+#define START_BOUNDARY_W(quad) (_cache[quad] & MASK_START_BOUNDARY_W)
+#define START_CORNER(quad) (_cache[quad] & MASK_START_CORNER)
+#define START_HOLE_N(quad) (_cache[quad] & MASK_START_HOLE_N)
+#define ANY_START(quad) ((_cache[quad] & MASK_ANY_START) != 0)
+#define LOOK_N(quad) (_cache[quad] & MASK_LOOK_N)
+#define LOOK_S(quad) (_cache[quad] & MASK_LOOK_S)
+#define NO_STARTS_IN_ROW(quad) (_cache[quad] & MASK_NO_STARTS_IN_ROW)
+#define NO_MORE_STARTS(quad) (_cache[quad] & MASK_NO_MORE_STARTS)
+// Contour line/fill goes to the left or right of quad middle (quad_as_tri only).
+#define LEFT_OF_MIDDLE(quad, is_upper) (MIDDLE_Z_LEVEL(quad) == (is_upper ? 2 : 0))
+
+
+template <typename Derived>
+BaseContourGenerator<Derived>::BaseContourGenerator(
+ const CoordinateArray& x, const CoordinateArray& y, const CoordinateArray& z,
+ const MaskArray& mask, bool corner_mask, LineType line_type, FillType fill_type,
+ bool quad_as_tri, ZInterp z_interp, index_t x_chunk_size, index_t y_chunk_size)
+ : _x(x),
+ _y(y),
+ _z(z),
+ _xptr(_x.data()),
+ _yptr(_y.data()),
+ _zptr(_z.data()),
+ _nx(_z.ndim() > 1 ? _z.shape(1) : 0),
+ _ny(_z.ndim() > 0 ? _z.shape(0) : 0),
+ _n(_nx*_ny),
+ _x_chunk_size(x_chunk_size > 0 ? std::min(x_chunk_size, _nx-1) : _nx-1),
+ _y_chunk_size(y_chunk_size > 0 ? std::min(y_chunk_size, _ny-1) : _ny-1),
+ _nx_chunks(static_cast<index_t>(std::ceil((_nx-1.0) / _x_chunk_size))),
+ _ny_chunks(static_cast<index_t>(std::ceil((_ny-1.0) / _y_chunk_size))),
+ _n_chunks(_nx_chunks*_ny_chunks),
+ _corner_mask(corner_mask),
+ _line_type(line_type),
+ _fill_type(fill_type),
+ _quad_as_tri(quad_as_tri),
+ _z_interp(z_interp),
+ _cache(new CacheItem[_n]),
+ _filled(false),
+ _lower_level(0.0),
+ _upper_level(0.0),
+ _identify_holes(false),
+ _output_chunked(false),
+ _direct_points(false),
+ _direct_line_offsets(false),
+ _direct_outer_offsets(false),
+ _outer_offsets_into_points(false),
+ _return_list_count(0)
+{
+ if (_x.ndim() != 2 || _y.ndim() != 2 || _z.ndim() != 2)
+ throw std::invalid_argument("x, y and z must all be 2D arrays");
+
+ if (_x.shape(1) != _nx || _x.shape(0) != _ny ||
+ _y.shape(1) != _nx || _y.shape(0) != _ny)
+ throw std::invalid_argument("x, y and z arrays must have the same shape");
+
+ if (_nx < 2 || _ny < 2)
+ throw std::invalid_argument("x, y and z must all be at least 2x2 arrays");
+
+ if (mask.ndim() != 0) { // ndim == 0 if mask is not set, which is valid.
+ if (mask.ndim() != 2)
+ throw std::invalid_argument("mask array must be a 2D array");
+
+ if (mask.shape(1) != _nx || mask.shape(0) != _ny)
+ throw std::invalid_argument(
+ "If mask is set it must be a 2D array with the same shape as z");
+ }
+
+ if (!supports_line_type(line_type))
+ throw std::invalid_argument("Unsupported LineType");
+
+ if (!supports_fill_type(fill_type))
+ throw std::invalid_argument("Unsupported FillType");
+
+ if (x_chunk_size < 0 || y_chunk_size < 0)
+ throw std::invalid_argument("x_chunk_size and y_chunk_size cannot be negative");
+
+ if (_z_interp == ZInterp::Log) {
+ const bool* mask_ptr = (mask.ndim() == 0 ? nullptr : mask.data());
+ for (index_t point = 0; point < _n; ++point) {
+ if ( (mask_ptr == nullptr || !mask_ptr[point]) && _zptr[point] <= 0.0)
+ throw std::invalid_argument("z values must be positive if using ZInterp.Log");
+ }
+ }
+
+ init_cache_grid(mask);
+}
+
+template <typename Derived>
+BaseContourGenerator<Derived>::~BaseContourGenerator()
+{
+ delete [] _cache;
+}
+
+template <typename Derived>
+typename BaseContourGenerator<Derived>::ZLevel
+ BaseContourGenerator<Derived>::calc_and_set_middle_z_level(index_t quad)
+{
+ ZLevel zlevel = z_to_zlevel(calc_middle_z(quad));
+ _cache[quad] |= (zlevel << 2);
+ return zlevel;
+}
+
+template <typename Derived>
+double BaseContourGenerator<Derived>::calc_middle_z(index_t quad) const
+{
+ assert(quad >= 0 && quad < _n);
+
+ switch (_z_interp) {
+ case ZInterp::Log:
+ return exp(0.25*(log(get_point_z(POINT_SW)) +
+ log(get_point_z(POINT_SE)) +
+ log(get_point_z(POINT_NW)) +
+ log(get_point_z(POINT_NE))));
+ default: // ZInterp::Linear
+ return 0.25*(get_point_z(POINT_SW) +
+ get_point_z(POINT_SE) +
+ get_point_z(POINT_NW) +
+ get_point_z(POINT_NE));
+ }
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::check_consistent_counts(const ChunkLocal& local) const
+{
+ if (local.total_point_count > 0) {
+ if (local.points.size != 2*local.total_point_count ||
+ local.points.current != local.points.start + 2*local.total_point_count) {
+ throw std::runtime_error(
+ "Inconsistent total_point_count for chunk " + std::to_string(local.chunk) +
+ ". This may indicate a bug in ContourPy.");
+ }
+ }
+ else {
+ if (local.points.size != 0 ||
+ local.points.start != nullptr || local.points.current != nullptr) {
+ throw std::runtime_error(
+ "Inconsistent zero total_point_count for chunk " + std::to_string(local.chunk) +
+ ". This may indicate a bug in ContourPy.");
+ }
+ }
+
+ if (local.line_count > 0) {
+ if (local.line_offsets.size != local.line_count + 1 ||
+ local.line_offsets.current == nullptr ||
+ local.line_offsets.current != local.line_offsets.start + local.line_count + 1) {
+ throw std::runtime_error(
+ "Inconsistent line_count for chunk " + std::to_string(local.chunk) +
+ ". This may indicate a bug in ContourPy.");
+ }
+ }
+ else {
+ if (local.line_offsets.size != 0 ||
+ local.line_offsets.start != nullptr || local.line_offsets.current != nullptr) {
+ throw std::runtime_error(
+ "Inconsistent zero line_count for chunk " + std::to_string(local.chunk) +
+ ". This may indicate a bug in ContourPy.");
+ }
+ }
+
+ if (_identify_holes && local.line_count > 0) {
+ if (local.outer_offsets.size != local.line_count - local.hole_count + 1 ||
+ local.outer_offsets.current == nullptr ||
+ local.outer_offsets.current != local.outer_offsets.start + local.line_count -
+ local.hole_count + 1) {
+ throw std::runtime_error(
+ "Inconsistent hole_count for chunk " + std::to_string(local.chunk) +
+ ". This may indicate a bug in ContourPy.");
+ }
+ }
+ else {
+ if (local.outer_offsets.size != 0 ||
+ local.outer_offsets.start != nullptr || local.outer_offsets.current != nullptr) {
+ throw std::runtime_error(
+ "Inconsistent zero hole_count for chunk " + std::to_string(local.chunk) +
+ ". This may indicate a bug in ContourPy.");
+ }
+ }
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::closed_line(
+ const Location& start_location, OuterOrHole outer_or_hole, ChunkLocal& local)
+{
+ assert(is_quad_in_chunk(start_location.quad, local));
+
+ Location location = start_location;
+ bool finished = false;
+ count_t point_count = 0;
+
+ if (outer_or_hole == Hole && local.pass == 0 && _identify_holes)
+ set_look_flags(start_location.quad);
+
+ while (!finished) {
+ if (location.on_boundary)
+ finished = follow_boundary(location, start_location, local, point_count);
+ else
+ finished = follow_interior(location, start_location, local, point_count);
+ location.on_boundary = !location.on_boundary;
+ }
+
+ if (local.pass > 0) {
+ assert(local.line_offsets.current = local.line_offsets.start + local.line_count);
+ *local.line_offsets.current++ = local.total_point_count;
+ if (outer_or_hole == Outer && _identify_holes) {
+ assert(local.outer_offsets.current ==
+ local.outer_offsets.start + local.line_count - local.hole_count);
+ if (_outer_offsets_into_points)
+ *local.outer_offsets.current++ = local.total_point_count;
+ else
+ *local.outer_offsets.current++ = local.line_count;
+ }
+ }
+
+ local.total_point_count += point_count;
+ local.line_count++;
+ if (outer_or_hole == Hole)
+ local.hole_count++;
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::closed_line_wrapper(
+ const Location& start_location, OuterOrHole outer_or_hole, ChunkLocal& local)
+{
+ assert(is_quad_in_chunk(start_location.quad, local));
+
+ if (local.pass == 0 || !_identify_holes) {
+ closed_line(start_location, outer_or_hole, local);
+ }
+ else {
+ assert(outer_or_hole == Outer);
+ local.look_up_quads.clear();
+
+ closed_line(start_location, outer_or_hole, local);
+
+ for (py::size_t i = 0; i < local.look_up_quads.size(); ++i) {
+ // Note that the collection can increase in size during this loop.
+ index_t quad = local.look_up_quads[i];
+
+ // Walk N to corresponding look S flag is reached.
+ quad = find_look_S(quad);
+
+ // Only 3 possible types of hole start: START_E, START_HOLE_N or START_CORNER for SW
+ // corner.
+ if (START_E(quad)) {
+ closed_line(Location(quad, -1, -_nx, Z_NE > 0, false), Hole, local);
+ }
+ else if (START_HOLE_N(quad)) {
+ closed_line(Location(quad, -1, -_nx, false, true), Hole, local);
+ }
+ else {
+ assert(START_CORNER(quad) && EXISTS_SW_CORNER(quad));
+ closed_line(Location(quad, _nx-1, -_nx-1, false, true), Hole, local);
+ }
+ }
+ }
+}
+
+template <typename Derived>
+FillType BaseContourGenerator<Derived>::default_fill_type()
+{
+ FillType fill_type = FillType::OuterOffset;
+ assert(supports_fill_type(fill_type));
+ return fill_type;
+}
+
+template <typename Derived>
+LineType BaseContourGenerator<Derived>::default_line_type()
+{
+ LineType line_type = LineType::Separate;
+ assert(supports_line_type(line_type));
+ return line_type;
+}
+
+template <typename Derived>
+py::sequence BaseContourGenerator<Derived>::filled(double lower_level, double upper_level)
+{
+ if (lower_level > upper_level)
+ throw std::invalid_argument("upper and lower levels are the wrong way round");
+
+ _filled = true;
+ _lower_level = lower_level;
+ _upper_level = upper_level;
+
+ _identify_holes = !(_fill_type == FillType::ChunkCombinedCode ||
+ _fill_type == FillType::ChunkCombinedOffset);
+ _output_chunked = !(_fill_type == FillType::OuterCode || _fill_type == FillType::OuterOffset);
+ _direct_points = _output_chunked;
+ _direct_line_offsets = (_fill_type == FillType::ChunkCombinedOffset||
+ _fill_type == FillType::ChunkCombinedOffsetOffset);
+ _direct_outer_offsets = (_fill_type == FillType::ChunkCombinedCodeOffset ||
+ _fill_type == FillType::ChunkCombinedOffsetOffset);
+ _outer_offsets_into_points = (_fill_type == FillType::ChunkCombinedCodeOffset);
+ _return_list_count = (_fill_type == FillType::ChunkCombinedCodeOffset ||
+ _fill_type == FillType::ChunkCombinedOffsetOffset) ? 3 : 2;
+
+ return march_wrapper();
+}
+
+template <typename Derived>
+index_t BaseContourGenerator<Derived>::find_look_S(index_t look_N_quad) const
+{
+ assert(_identify_holes);
+
+ // Might need to be careful when looking in the same quad as the LOOK_UP.
+ index_t quad = look_N_quad;
+
+ // look_S quad must have 1 of only 3 possible types of hole start (START_E, START_HOLE_N,
+ // START_CORNER for SW corner) but it may have other starts as well.
+
+ // Start quad may be both a look_N and look_S quad. Only want to stop search here if look_S
+ // hole start is N of look_N.
+
+ if (!LOOK_S(quad)) {
+ do
+ {
+ quad += _nx;
+ assert(quad >= 0 && quad < _n);
+ assert(EXISTS_ANY(quad));
+ } while (!LOOK_S(quad));
+ }
+
+ return quad;
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::follow_boundary(
+ Location& location, const Location& start_location, ChunkLocal& local, count_t& point_count)
+{
+ // forward values for boundaries:
+ // -1 = N boundary, E to W.
+ // 1 = S boundary, W to E.
+ // -_nx = W boundary, N to S.
+ // _nx = E boundary, S to N.
+ // -_nx+1 = NE corner, NW to SE.
+ // _nx+1 = NW corner, SW to NE.
+ // -_nx-1 = SE corner, NE to SW.
+ // _nx-1 = SW corner, SE to NW.
+
+ assert(is_quad_in_chunk(start_location.quad, local));
+ assert(is_quad_in_chunk(location.quad, local));
+
+ // Local variables for faster access.
+ auto quad = location.quad;
+ auto forward = location.forward;
+ auto left = location.left;
+ auto start_quad = start_location.quad;
+ auto start_forward = start_location.forward;
+ auto start_left = start_location.left;
+ auto pass = local.pass;
+ double*& points = local.points.current;
+
+ auto start_point = get_boundary_start_point(location);
+ auto end_point = start_point + forward;
+
+ assert(is_point_in_chunk(start_point, local));
+ assert(is_point_in_chunk(end_point, local));
+
+ auto start_z = Z_LEVEL(start_point);
+ auto end_z = Z_LEVEL(end_point);
+
+ // Add new point, somewhere along start line. May be at start point of edge if this is a
+ // boundary start.
+ point_count++;
+ if (pass > 0) {
+ if (start_z == 1)
+ get_point_xy(start_point, points);
+ else // start_z != 1
+ interp(start_point, end_point, location.is_upper, points);
+ }
+
+ bool finished = false;
+ while (true) {
+ assert(is_quad_in_chunk(quad, local));
+
+ if (quad == start_quad && forward == start_forward && left == start_left) {
+ if (start_location.on_boundary && point_count > 1) {
+ // Polygon closed.
+ finished = true;
+ break;
+ }
+ }
+ else if (pass == 0) {
+ // Clear unwanted start locations.
+ if (left == _nx) {
+ if (START_BOUNDARY_S(quad)) {
+ assert(forward == 1);
+ _cache[quad] &= ~MASK_START_BOUNDARY_S;
+ }
+ }
+ else if (forward == -_nx) {
+ if (START_BOUNDARY_W(quad)) {
+ assert(left == 1);
+ _cache[quad] &= ~MASK_START_BOUNDARY_W;
+ }
+ }
+ else if (left == -_nx) {
+ if (START_HOLE_N(quad)) {
+ assert(forward == -1);
+ _cache[quad] &= ~MASK_START_HOLE_N;
+ }
+ }
+ else {
+ switch (EXISTS_ANY_CORNER(quad)) {
+ case MASK_EXISTS_NE_CORNER:
+ if (left == _nx+1) {
+ assert(forward == -_nx+1);
+ _cache[quad] &= ~MASK_START_CORNER;
+ }
+ break;
+ case MASK_EXISTS_NW_CORNER:
+ if (forward == _nx+1) {
+ assert(left == _nx-1);
+ _cache[quad] &= ~MASK_START_CORNER;
+ }
+ break;
+ case MASK_EXISTS_SE_CORNER:
+ if (forward == -_nx-1) {
+ assert(left == -_nx+1);
+ _cache[quad] &= ~MASK_START_CORNER;
+ }
+ break;
+ case MASK_EXISTS_SW_CORNER:
+ if (left == -_nx-1) {
+ assert(forward == _nx-1);
+ _cache[quad] &= ~MASK_START_CORNER;
+ }
+ break;
+ default:
+ // Not a corner.
+ break;
+ }
+ }
+ }
+
+ // Check if need to leave boundary into interior.
+ if (end_z != 1) {
+ location.is_upper = (end_z == 2); // Leave via this level.
+ auto temp = forward;
+ forward = left;
+ left = -temp;
+ break;
+ }
+
+ // Add end point.
+ point_count++;
+ if (pass > 0) {
+ get_point_xy(end_point, points);
+
+ if (LOOK_N(quad) && _identify_holes &&
+ (left == _nx || left == _nx+1 || forward == _nx+1)) {
+ assert(BOUNDARY_N(quad-_nx) || EXISTS_NE_CORNER(quad) || EXISTS_NW_CORNER(quad));
+ local.look_up_quads.push_back(quad);
+ }
+ }
+
+ move_to_next_boundary_edge(quad, forward, left);
+
+ start_point = end_point;
+ start_z = end_z;
+ end_point = start_point + forward;
+ end_z = Z_LEVEL(end_point);
+ }
+
+ location.quad = quad;
+ location.forward = forward;
+ location.left = left;
+
+ return finished;
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::follow_interior(
+ Location& location, const Location& start_location, ChunkLocal& local, count_t& point_count)
+{
+ // Adds the start point in each quad visited, but not the end point unless closing the polygon.
+ // Only need to consider a single level of course.
+ assert(is_quad_in_chunk(start_location.quad, local));
+ assert(is_quad_in_chunk(location.quad, local));
+
+ // Local variables for faster access.
+ auto quad = location.quad;
+ auto forward = location.forward;
+ auto left = location.left;
+ auto is_upper = location.is_upper;
+ auto start_quad = start_location.quad;
+ auto start_forward = start_location.forward;
+ auto start_left = start_location.left;
+ auto pass = local.pass;
+ double*& points = local.points.current;
+
+ // left direction, and indices of points on entry edge.
+ bool start_corner_diagonal = false;
+ auto left_point = get_interior_start_left_point(location, start_corner_diagonal);
+ auto right_point = left_point - left;
+ bool want_look_N = _identify_holes && pass > 0;
+
+ bool finished = false; // Whether finished line, i.e. returned to start.
+ while (true) {
+ assert(is_quad_in_chunk(quad, local));
+ assert(is_point_in_chunk(left_point, local));
+ assert(is_point_in_chunk(right_point, local));
+
+ if (pass > 0)
+ interp(left_point, right_point, is_upper, points);
+ point_count++;
+
+ if (quad == start_quad && forward == start_forward &&
+ left == start_left && is_upper == start_location.is_upper &&
+ !start_location.on_boundary && point_count > 1) {
+ finished = true; // Polygon closed, exit immediately.
+ break;
+ }
+
+ // Indices of the opposite points.
+ auto opposite_left_point = left_point + forward;
+ auto opposite_right_point = right_point + forward;
+ bool corner_opposite_is_right = false; // Only used for corners.
+
+ if (start_corner_diagonal) {
+ // To avoid dealing with diagonal forward and left below, switch to direction 45 degrees
+ // to left, e.g. NW corner faces west using forward == -1.
+ corner_opposite_is_right = true;
+ switch (EXISTS_ANY_CORNER(quad)) {
+ case MASK_EXISTS_NW_CORNER:
+ forward = -1;
+ left = -_nx;
+ opposite_left_point = opposite_right_point = quad-1;
+ break;
+ case MASK_EXISTS_NE_CORNER:
+ forward = _nx;
+ left = -1;
+ opposite_left_point = opposite_right_point = quad;
+ break;
+ case MASK_EXISTS_SW_CORNER:
+ forward = -_nx;
+ left = 1;
+ opposite_left_point = opposite_right_point = quad-_nx-1;
+ break;
+ default:
+ assert(EXISTS_SE_CORNER(quad));
+ forward = 1;
+ left = _nx;
+ opposite_left_point = opposite_right_point = quad-_nx;
+ break;
+ }
+ }
+
+ // z-levels of the opposite points.
+ ZLevel z_opposite_left = Z_LEVEL(opposite_left_point);
+ ZLevel z_opposite_right = Z_LEVEL(opposite_right_point);
+
+ Direction direction = Direction::Right;
+ ZLevel z_test = is_upper ? 2 : 0;
+
+ if (EXISTS_QUAD(quad)) {
+ if (z_opposite_left == z_test) {
+ if (z_opposite_right == z_test || MIDDLE_Z_LEVEL(quad) == z_test)
+ direction = Direction::Left;
+ }
+ else if (z_opposite_right == z_test)
+ direction = Direction::Straight;
+ }
+ else if (start_corner_diagonal) {
+ direction = (z_opposite_left == z_test) ? Direction::Straight : Direction::Right;
+ }
+ else {
+ switch (EXISTS_ANY_CORNER(quad)) {
+ case MASK_EXISTS_NW_CORNER:
+ corner_opposite_is_right = (forward == -_nx);
+ break;
+ case MASK_EXISTS_NE_CORNER:
+ corner_opposite_is_right = (forward == -1);
+ break;
+ case MASK_EXISTS_SW_CORNER:
+ corner_opposite_is_right = (forward == 1);
+ break;
+ default:
+ assert(EXISTS_SE_CORNER(quad));
+ corner_opposite_is_right = (forward == _nx);
+ break;
+ }
+
+ if (corner_opposite_is_right)
+ direction = (z_opposite_right == z_test) ? Direction::Straight : Direction::Right;
+ else
+ direction = (z_opposite_left == z_test) ? Direction::Left : Direction::Straight;
+ }
+
+ // Clear unwanted start locations.
+ if (pass == 0 && !(quad == start_quad && forward == start_forward && left == start_left)) {
+ if (START_E(quad) && forward == -1 && left == -_nx && direction == Direction::Right &&
+ (is_upper ? Z_NE > 0 : Z_NE < 2)) {
+ _cache[quad] &= ~MASK_START_E; // E high if is_upper else low.
+
+ if (!_filled && quad < start_location.quad)
+ // Already counted points from here onwards.
+ break;
+ }
+ else if (START_N(quad) && forward == -_nx && left == 1 &&
+ direction == Direction::Left && (is_upper ? Z_NW > 0 : Z_NW < 2)) {
+ _cache[quad] &= ~MASK_START_N; // E high if is_upper else low.
+
+ if (!_filled && quad < start_location.quad)
+ // Already counted points from here onwards.
+ break;
+ }
+ }
+
+ // Extra quad_as_tri points.
+ if (_quad_as_tri && EXISTS_QUAD(quad)) {
+ if (pass == 0) {
+ switch (direction) {
+ case Direction::Left:
+ point_count += (LEFT_OF_MIDDLE(quad, is_upper) ? 1 : 3);
+ break;
+ case Direction::Right:
+ point_count += (LEFT_OF_MIDDLE(quad, is_upper) ? 3 : 1);
+ break;
+ case Direction::Straight:
+ point_count += 2;
+ break;
+ }
+ }
+ else { // pass == 1
+ auto mid_x = get_middle_x(quad);
+ auto mid_y = get_middle_y(quad);
+ auto mid_z = calc_middle_z(quad);
+
+ switch (direction) {
+ case Direction::Left:
+ if (LEFT_OF_MIDDLE(quad, is_upper)) {
+ interp(left_point, mid_x, mid_y, mid_z, is_upper, points);
+ point_count++;
+ }
+ else {
+ interp(right_point, mid_x, mid_y, mid_z, is_upper, points);
+ interp(opposite_right_point, mid_x, mid_y, mid_z, is_upper, points);
+ interp(opposite_left_point, mid_x, mid_y, mid_z, is_upper, points);
+ point_count += 3;
+ }
+ break;
+ case Direction::Right:
+ if (LEFT_OF_MIDDLE(quad, is_upper)) {
+ interp(left_point, mid_x, mid_y, mid_z, is_upper, points);
+ interp(opposite_left_point, mid_x, mid_y, mid_z, is_upper, points);
+ interp(opposite_right_point, mid_x, mid_y, mid_z, is_upper, points);
+ point_count += 3;
+ }
+ else {
+ interp(right_point, mid_x, mid_y, mid_z, is_upper, points);
+ point_count++;
+ }
+ break;
+ case Direction::Straight:
+ if (LEFT_OF_MIDDLE(quad, is_upper)) {
+ interp(left_point, mid_x, mid_y, mid_z, is_upper, points);
+ interp(opposite_left_point, mid_x, mid_y, mid_z, is_upper, points);
+ }
+ else {
+ interp(right_point, mid_x, mid_y, mid_z, is_upper, points);
+ interp(opposite_right_point, mid_x, mid_y, mid_z, is_upper, points);
+ }
+ point_count += 2;
+ break;
+ }
+ }
+ }
+
+ bool reached_boundary = false;
+
+ // Determine entry edge and left and right points of next quad.
+ // Do not update quad index yet.
+ switch (direction) {
+ case Direction::Left: {
+ auto temp = forward;
+ forward = left;
+ left = -temp;
+ // left_point unchanged.
+ right_point = opposite_left_point;
+ break;
+ }
+ case Direction::Right: {
+ auto temp = forward;
+ forward = -left;
+ left = temp;
+ left_point = opposite_right_point;
+ // right_point unchanged.
+ break;
+ }
+ case Direction::Straight: {
+ if (EXISTS_QUAD(quad)) { // Straight on in quad.
+ // forward and left stay the same.
+ left_point = opposite_left_point;
+ right_point = opposite_right_point;
+ }
+ else if (start_corner_diagonal) { // Straight on diagonal start corner.
+ // left point unchanged.
+ right_point = opposite_right_point;
+ }
+ else { // Straight on in a corner reaches boundary.
+ assert(EXISTS_ANY_CORNER(quad));
+ reached_boundary = true;
+
+ if (corner_opposite_is_right) {
+ // left_point unchanged.
+ right_point = opposite_right_point;
+ }
+ else {
+ left_point = opposite_left_point;
+ // right_point unchanged.
+ }
+
+ // Set forward and left for correct exit along boundary.
+ switch (EXISTS_ANY_CORNER(quad)) {
+ case MASK_EXISTS_NW_CORNER:
+ forward = _nx+1;
+ left = _nx-1;
+ break;
+ case MASK_EXISTS_NE_CORNER:
+ forward = -_nx+1;
+ left = _nx+1;
+ break;
+ case MASK_EXISTS_SW_CORNER:
+ forward = _nx-1;
+ left = -_nx-1;
+ break;
+ default:
+ assert(EXISTS_SE_CORNER(quad));
+ forward = -_nx-1;
+ left = -_nx+1;
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (want_look_N && LOOK_N(quad) && forward == 1) {
+ // Only consider look_N if pass across E edge of this quad.
+ // Care needed if both look_N and look_S set in quad because this line corresponds to
+ // only one of them, so want to ignore the look_N if it is the other line otherwise it
+ // will be double counted.
+ if (!LOOK_S(quad) || (is_upper ? Z_NE < 2 : Z_NE > 0))
+ local.look_up_quads.push_back(quad);
+ }
+
+ // Check if reached NSEW boundary; already checked and noted if reached corner boundary.
+ if (!reached_boundary) {
+ if (forward > 0)
+ reached_boundary = (forward == 1 ? BOUNDARY_E(quad) : BOUNDARY_N(quad));
+ else // forward < 0
+ reached_boundary = (forward == -1 ? BOUNDARY_W(quad) : BOUNDARY_S(quad));
+
+ if (reached_boundary) {
+ auto temp = forward;
+ forward = left;
+ left = -temp;
+ }
+ }
+
+ // If reached a boundary, return.
+ if (reached_boundary) {
+ if (!_filled) {
+ point_count++;
+ if (pass > 0)
+ interp(left_point, right_point, false, points);
+ }
+ break;
+ }
+
+ quad += forward;
+ start_corner_diagonal = false;
+ }
+
+ location.quad = quad;
+ location.forward = forward;
+ location.left = left;
+ location.is_upper = is_upper;
+
+ return finished;
+}
+
+template <typename Derived>
+index_t BaseContourGenerator<Derived>::get_boundary_start_point(const Location& location) const
+{
+ auto quad = location.quad;
+ auto forward = location.forward;
+ auto left = location.left;
+ index_t start_point = -1;
+
+ if (forward > 0) {
+ if (forward == _nx) {
+ assert(left == -1);
+ start_point = quad-_nx;
+ }
+ else if (left == _nx) {
+ assert(forward == 1);
+ start_point = quad-_nx-1;
+ }
+ else if (EXISTS_SW_CORNER(quad)) {
+ assert(forward == _nx-1 && left == -_nx-1);
+ start_point = quad-_nx;
+ }
+ else {
+ assert(EXISTS_NW_CORNER(quad) && forward == _nx+1 && left == _nx-1);
+ start_point = quad-_nx-1;
+ }
+ }
+ else { // forward < 0
+ if (forward == -_nx) {
+ assert(left == 1);
+ start_point = quad-1;
+ }
+ else if (left == -_nx) {
+ assert(forward == -1);
+ start_point = quad;
+ }
+ else if (EXISTS_NE_CORNER(quad)) {
+ assert(forward == -_nx+1 && left == _nx+1);
+ start_point = quad-1;
+ }
+ else {
+ assert(EXISTS_SE_CORNER(quad) && forward == -_nx-1 && left == -_nx+1);
+ start_point = quad;
+ }
+ }
+ return start_point;
+}
+
+template <typename Derived>
+py::tuple BaseContourGenerator<Derived>::get_chunk_count() const
+{
+ return py::make_tuple(_ny_chunks, _nx_chunks);
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::get_chunk_limits(index_t chunk, ChunkLocal& local) const
+{
+ assert(chunk >= 0 && chunk < _n_chunks && "chunk index out of bounds");
+
+ local.chunk = chunk;
+
+ auto ichunk = chunk % _nx_chunks;
+ auto jchunk = chunk / _nx_chunks;
+
+ local.istart = ichunk*_x_chunk_size + 1;
+ local.iend = (ichunk < _nx_chunks-1 ? (ichunk+1)*_x_chunk_size : _nx-1);
+
+ local.jstart = jchunk*_y_chunk_size + 1;
+ local.jend = (jchunk < _ny_chunks-1 ? (jchunk+1)*_y_chunk_size : _ny-1);
+}
+
+template <typename Derived>
+py::tuple BaseContourGenerator<Derived>::get_chunk_size() const
+{
+ return py::make_tuple(_y_chunk_size, _x_chunk_size);
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::get_corner_mask() const
+{
+ return _corner_mask;
+}
+
+template <typename Derived>
+FillType BaseContourGenerator<Derived>::get_fill_type() const
+{
+ return _fill_type;
+}
+
+template <typename Derived>
+index_t BaseContourGenerator<Derived>::get_interior_start_left_point(
+ const Location& location, bool& start_corner_diagonal) const
+{
+ auto quad = location.quad;
+ auto forward = location.forward;
+ auto left = location.left;
+ index_t left_point = -1;
+
+ if (forward > 0) {
+ if (forward == _nx) {
+ assert(left == -1);
+ left_point = quad-_nx-1;
+ }
+ else if (left == _nx) {
+ assert(forward == 1);
+ left_point = quad-1;
+ }
+ else if (EXISTS_NW_CORNER(quad)) {
+ assert(forward == _nx-1 && left == -_nx-1);
+ left_point = quad-_nx-1;
+ start_corner_diagonal = true;
+ }
+ else {
+ assert(EXISTS_NE_CORNER(quad) && forward == _nx+1 && left == _nx-1);
+ left_point = quad-1;
+ start_corner_diagonal = true;
+ }
+ }
+ else { // forward < 0
+ if (forward == -_nx) {
+ assert(left == 1);
+ left_point = quad;
+ }
+ else if (left == -_nx) {
+ assert(forward == -1);
+ left_point = quad-_nx;
+ }
+ else if (EXISTS_SW_CORNER(quad)) {
+ assert(forward == -_nx-1 && left == -_nx+1);
+ left_point = quad-_nx;
+ start_corner_diagonal = true;
+ }
+ else {
+ assert(EXISTS_SE_CORNER(quad) && forward == -_nx+1 && left == _nx+1);
+ left_point = quad;
+ start_corner_diagonal = true;
+ }
+ }
+ return left_point;
+}
+
+template <typename Derived>
+double BaseContourGenerator<Derived>::get_interp_fraction(double z0, double z1, double level) const
+{
+ switch (_z_interp) {
+ case ZInterp::Log:
+ // Equivalent to
+ // (log(z1) - log(level)) / (log(z1) - log(z0))
+ // Same result obtained regardless of logarithm base.
+ return log(z1/level) / log(z1/z0);
+ default: // ZInterp::Linear
+ return (z1 - level) / (z1 - z0);
+ }
+}
+
+template <typename Derived>
+LineType BaseContourGenerator<Derived>::get_line_type() const
+{
+ return _line_type;
+}
+
+template <typename Derived>
+double BaseContourGenerator<Derived>::get_middle_x(index_t quad) const
+{
+ return 0.25*(get_point_x(POINT_SW) + get_point_x(POINT_SE) +
+ get_point_x(POINT_NW) + get_point_x(POINT_NE));
+}
+
+template <typename Derived>
+double BaseContourGenerator<Derived>::get_middle_y(index_t quad) const
+{
+ return 0.25*(get_point_y(POINT_SW) + get_point_y(POINT_SE) +
+ get_point_y(POINT_NW) + get_point_y(POINT_NE));
+}
+
+template <typename Derived>
+index_t BaseContourGenerator<Derived>::get_n_chunks() const
+{
+ return _n_chunks;
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::get_point_xy(index_t point, double*& points) const
+{
+ assert(point >= 0 && point < _n && "point index out of bounds");
+ *points++ = _xptr[point];
+ *points++ = _yptr[point];
+}
+
+template <typename Derived>
+double BaseContourGenerator<Derived>::get_point_x(index_t point) const
+{
+ assert(point >= 0 && point < _n && "point index out of bounds");
+ return _xptr[point];
+}
+
+template <typename Derived>
+double BaseContourGenerator<Derived>::get_point_y(index_t point) const
+{
+ assert(point >= 0 && point < _n && "point index out of bounds");
+ return _yptr[point];
+}
+
+template <typename Derived>
+double BaseContourGenerator<Derived>::get_point_z(index_t point) const
+{
+ assert(point >= 0 && point < _n && "point index out of bounds");
+ return _zptr[point];
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::get_quad_as_tri() const
+{
+ return _quad_as_tri;
+}
+
+template <typename Derived>
+ZInterp BaseContourGenerator<Derived>::get_z_interp() const
+{
+ return _z_interp;
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::has_direct_line_offsets() const
+{
+ return _direct_line_offsets;
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::has_direct_outer_offsets() const
+{
+ return _direct_outer_offsets;
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::has_direct_points() const
+{
+ return _direct_points;
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::init_cache_grid(const MaskArray& mask)
+{
+ index_t i, j, quad;
+ if (mask.ndim() == 0) {
+ // No mask, easy to calculate quad existence and boundaries together.
+ for (j = 0, quad = 0; j < _ny; ++j) {
+ for (i = 0; i < _nx; ++i, ++quad) {
+ _cache[quad] = 0;
+
+ if (i > 0 && j > 0)
+ _cache[quad] |= MASK_EXISTS_QUAD;
+
+ if ((i % _x_chunk_size == 0 || i == _nx-1) && j > 0)
+ _cache[quad] |= MASK_BOUNDARY_E;
+
+ if ((j % _y_chunk_size == 0 || j == _ny-1) && i > 0)
+ _cache[quad] |= MASK_BOUNDARY_N;
+ }
+ }
+ }
+ else {
+ // Could maybe speed this up and just have a single pass.
+ // Care would be needed with lookback of course.
+ const bool* mask_ptr = mask.data();
+
+ // Have mask so use two stages.
+ // Stage 1, determine if quads/corners exist.
+ quad = 0;
+ for (j = 0; j < _ny; ++j) {
+ for (i = 0; i < _nx; ++i, ++quad) {
+ _cache[quad] = 0;
+
+ if (i > 0 && j > 0) {
+ unsigned int config = (mask_ptr[POINT_NW] << 3) |
+ (mask_ptr[POINT_NE] << 2) |
+ (mask_ptr[POINT_SW] << 1) |
+ (mask_ptr[POINT_SE] << 0);
+ if (_corner_mask) {
+ switch (config) {
+ case 0: _cache[quad] = MASK_EXISTS_QUAD; break;
+ case 1: _cache[quad] = MASK_EXISTS_NW_CORNER; break;
+ case 2: _cache[quad] = MASK_EXISTS_NE_CORNER; break;
+ case 4: _cache[quad] = MASK_EXISTS_SW_CORNER; break;
+ case 8: _cache[quad] = MASK_EXISTS_SE_CORNER; break;
+ default:
+ // Do nothing, quad is masked out.
+ break;
+ }
+ }
+ else if (config == 0)
+ _cache[quad] = MASK_EXISTS_QUAD;
+ }
+ }
+ }
+
+ // Stage 2, calculate N and E boundaries.
+ quad = 0;
+ for (j = 0; j < _ny; ++j) {
+ bool j_chunk_boundary = j % _y_chunk_size == 0;
+
+ for (i = 0; i < _nx; ++i, ++quad) {
+ bool i_chunk_boundary = i % _x_chunk_size == 0;
+
+ if (_corner_mask) {
+ bool exists_E_edge = EXISTS_E_EDGE(quad);
+ bool E_exists_W_edge = (i < _nx-1 && EXISTS_W_EDGE(quad+1));
+ bool exists_N_edge = EXISTS_N_EDGE(quad);
+ bool N_exists_S_edge = (j < _ny-1 && EXISTS_S_EDGE(quad+_nx));
+
+ if (exists_E_edge != E_exists_W_edge ||
+ (i_chunk_boundary && exists_E_edge && E_exists_W_edge))
+ _cache[quad] |= MASK_BOUNDARY_E;
+
+ if (exists_N_edge != N_exists_S_edge ||
+ (j_chunk_boundary && exists_N_edge && N_exists_S_edge))
+ _cache[quad] |= MASK_BOUNDARY_N;
+ }
+ else {
+ bool E_exists_quad = (i < _nx-1 && EXISTS_QUAD(quad+1));
+ bool N_exists_quad = (j < _ny-1 && EXISTS_QUAD(quad+_nx));
+ bool exists = EXISTS_QUAD(quad);
+
+ if (exists != E_exists_quad || (i_chunk_boundary && exists && E_exists_quad))
+ _cache[quad] |= MASK_BOUNDARY_E;
+
+ if (exists != N_exists_quad || (j_chunk_boundary && exists && N_exists_quad))
+ _cache[quad] |= MASK_BOUNDARY_N;
+ }
+ }
+ }
+ }
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::init_cache_levels_and_starts(const ChunkLocal* local)
+{
+ bool ordered_chunks = (local == nullptr);
+
+ // This function initialises the cache z-levels and starts for either a single chunk or the
+ // whole domain. If a single chunk, only the quads contained in the chunk are calculated and
+ // this includes the z-levels of the points that on the NE corners of those quads. In addition,
+ // chunks that are on the W (starting at i=1) also calculate the most westerly points (i=0),
+ // and similarly chunks that are on the S (starting at j=1) also calculate the most southerly
+ // points (j=0). Non W/S chunks do not do this as their neighboring chunks to the W/S are
+ // responsible for it. If ordered_chunks is true then those W/S points will already have had
+ // their cache items set so that their z-levels can be read from the cache as usual. But if
+ // ordered_chunks is false then we cannot rely upon those neighboring W/S points having their
+ // cache items already set and so must temporarily calculate those z-levels rather than reading
+ // the cache.
+
+ constexpr CacheItem keep_mask = (MASK_EXISTS_ANY | MASK_BOUNDARY_N | MASK_BOUNDARY_E);
+
+ index_t istart, iend, jstart, jend; // Loop indices.
+ index_t chunk_istart; // Actual start i-index of chunk.
+
+ if (local != nullptr) {
+ chunk_istart = local->istart;
+ istart = chunk_istart > 1 ? chunk_istart : 0;
+ iend = local->iend;
+ jstart = local->jstart > 1 ? local->jstart : 0;
+ jend = local->jend;
+ }
+ else {
+ chunk_istart = 1;
+ istart = 0;
+ iend = _nx-1;
+ jstart = 0;
+ jend = _ny-1;
+ }
+
+ index_t j_final_start = jstart - 1;
+ bool calc_W_z_level = (!ordered_chunks && istart == chunk_istart);
+
+ for (index_t j = jstart; j <= jend; ++j) {
+ index_t quad = istart + j*_nx;
+ const double* z_ptr = _zptr + quad;
+ bool start_in_row = false;
+ bool calc_S_z_level = (!ordered_chunks && j == jstart);
+
+ // z-level of NW point not needed if i == 0.
+ ZLevel z_nw = (istart == 0) ? 0 : (calc_W_z_level ? z_to_zlevel(*(z_ptr-1)) : Z_NW);
+
+ // z-level of SW point not needed if i == 0 or j == 0.
+ ZLevel z_sw = (istart == 0 || j == 0) ? 0 :
+ ((calc_W_z_level || calc_S_z_level) ? z_to_zlevel(*(z_ptr-_nx-1)) : Z_SW);
+
+ for (index_t i = istart; i <= iend; ++i, ++quad, ++z_ptr) {
+ // z-level of SE point not needed if j == 0.
+ ZLevel z_se = (j == 0) ? 0 : (calc_S_z_level ? z_to_zlevel(*(z_ptr-_nx)) : Z_SE);
+
+ _cache[quad] &= keep_mask;
+
+ // Calculate and cache z-level of NE point.
+ ZLevel z_ne = z_to_zlevel(*z_ptr);
+ _cache[quad] |= z_ne;
+
+ switch (EXISTS_ANY(quad)) {
+ case MASK_EXISTS_QUAD:
+ if (_filled) {
+ switch ((z_nw << 6) | (z_ne << 4) | (z_sw << 2) | z_se) { // config
+ case 1: // 0001
+ case 2: // 0002
+ case 17: // 0101
+ case 18: // 0102
+ case 34: // 0202
+ case 68: // 1010
+ case 102: // 1212
+ case 136: // 2020
+ case 152: // 2120
+ case 153: // 2121
+ case 168: // 2220
+ case 169: // 2221
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_S;
+ start_in_row = true;
+ }
+ break;
+ case 4: // 0010
+ case 5: // 0011
+ case 6: // 0012
+ case 8: // 0020
+ case 9: // 0021
+ case 21: // 0111
+ case 22: // 0112
+ case 25: // 0121
+ case 38: // 0212
+ case 72: // 1020
+ case 98: // 1202
+ case 132: // 2010
+ case 145: // 2101
+ case 148: // 2110
+ case 149: // 2111
+ case 161: // 2201
+ case 162: // 2202
+ case 164: // 2210
+ case 165: // 2211
+ case 166: // 2212
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 10: // 0022
+ case 26: // 0122
+ case 42: // 0222
+ case 64: // 1000
+ case 106: // 1222
+ case 128: // 2000
+ case 144: // 2100
+ case 160: // 2200
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_W(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row = true;
+ }
+ break;
+ case 16: // 0100
+ case 154: // 2122
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ _cache[quad] |= MASK_START_N;
+ start_in_row = true;
+ break;
+ case 20: // 0110
+ case 24: // 0120
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) == 0) _cache[quad] |= MASK_START_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 32: // 0200
+ case 138: // 2022
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ _cache[quad] |= MASK_START_E;
+ _cache[quad] |= MASK_START_N;
+ start_in_row = true;
+ break;
+ case 33: // 0201
+ case 69: // 1011
+ case 70: // 1012
+ case 100: // 1210
+ case 101: // 1211
+ case 137: // 2021
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ _cache[quad] |= MASK_START_E;
+ start_in_row = true;
+ break;
+ case 36: // 0210
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) == 0) _cache[quad] |= MASK_START_N;
+ _cache[quad] |= MASK_START_E;
+ start_in_row = true;
+ break;
+ case 37: // 0211
+ case 73: // 1021
+ case 97: // 1201
+ case 133: // 2011
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ _cache[quad] |= MASK_START_E;
+ start_in_row = true;
+ break;
+ case 40: // 0220
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) < 2) _cache[quad] |= MASK_START_E;
+ if (MIDDLE_Z_LEVEL(quad) == 0) _cache[quad] |= MASK_START_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 41: // 0221
+ case 104: // 1220
+ case 105: // 1221
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) < 2) _cache[quad] |= MASK_START_E;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 65: // 1001
+ case 66: // 1002
+ case 129: // 2001
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) > 0) _cache[quad] |= MASK_START_E;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 74: // 1022
+ case 96: // 1200
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ _cache[quad] |= MASK_START_E;
+ start_in_row = true;
+ break;
+ case 80: // 1100
+ case 90: // 1122
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (BOUNDARY_N(quad) && !START_HOLE_N(quad-1) &&
+ j % _y_chunk_size > 0 && j != _ny-1 && i % _x_chunk_size > 1)
+ _cache[quad] |= MASK_START_HOLE_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 81: // 1101
+ case 82: // 1102
+ case 88: // 1120
+ case 89: // 1121
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (BOUNDARY_N(quad) && !START_HOLE_N(quad-1) &&
+ j % _y_chunk_size > 0 && j != _ny-1 && i % _x_chunk_size > 1)
+ _cache[quad] |= MASK_START_HOLE_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 84: // 1110
+ case 85: // 1111
+ case 86: // 1112
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_N(quad) && !START_HOLE_N(quad-1) &&
+ j % _y_chunk_size > 0 && j != _ny-1 && i % _x_chunk_size > 1)
+ _cache[quad] |= MASK_START_HOLE_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 130: // 2002
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) > 0) _cache[quad] |= MASK_START_E;
+ if (MIDDLE_Z_LEVEL(quad) == 2) _cache[quad] |= MASK_START_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 134: // 2012
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) == 2) _cache[quad] |= MASK_START_N;
+ _cache[quad] |= MASK_START_E;
+ start_in_row = true;
+ break;
+ case 146: // 2102
+ case 150: // 2112
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (MIDDLE_Z_LEVEL(quad) == 2) _cache[quad] |= MASK_START_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ }
+ }
+ else { // !_filled quad
+ switch ((z_nw << 3) | (z_ne << 2) | (z_sw << 1) | z_se) { // config
+ case 1: // 0001
+ case 3: // 0011
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_E(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_E;
+ start_in_row = true;
+ }
+ break;
+ case 2: // 0010
+ case 10: // 1010
+ case 14: // 1110
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_S(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_S;
+ start_in_row = true;
+ }
+ break;
+ case 4: // 0100
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_N(quad))
+ _cache[quad] |= MASK_START_BOUNDARY_N;
+ else if (!BOUNDARY_E(quad))
+ _cache[quad] |= MASK_START_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 5: // 0101
+ case 7: // 0111
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_N(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_N;
+ start_in_row = true;
+ }
+ break;
+ case 6: // 0110
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_N(quad))
+ _cache[quad] |= MASK_START_BOUNDARY_N;
+ else if (!BOUNDARY_E(quad) && MIDDLE_Z_LEVEL(quad) == 0)
+ _cache[quad] |= MASK_START_N;
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 8: // 1000
+ case 12: // 1100
+ case 13: // 1101
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_W(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row = true;
+ }
+ break;
+ case 9: // 1001
+ calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_E(quad))
+ _cache[quad] |= MASK_START_BOUNDARY_E;
+ else if (!BOUNDARY_N(quad) && MIDDLE_Z_LEVEL(quad) == 1)
+ _cache[quad] |= MASK_START_E;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 11: // 1011
+ if (_quad_as_tri) calc_and_set_middle_z_level(quad);
+ if (BOUNDARY_E(quad))
+ _cache[quad] |= MASK_START_BOUNDARY_E;
+ else if (!BOUNDARY_N(quad))
+ _cache[quad] |= MASK_START_E;
+ start_in_row |= ANY_START(quad);
+ break;
+ }
+ }
+ break;
+ case MASK_EXISTS_NW_CORNER:
+ if (_filled) {
+ switch ((z_nw << 4) | (z_ne << 2) | z_sw) { // config
+ case 1: // 001
+ case 5: // 011
+ case 9: // 021
+ case 10: // 022
+ case 16: // 100
+ case 17: // 101
+ case 25: // 121
+ case 26: // 122
+ case 32: // 200
+ case 33: // 201
+ case 37: // 211
+ case 41: // 221
+ if (BOUNDARY_W(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row = true;
+ }
+ break;
+ case 2: // 002
+ case 6: // 012
+ case 18: // 102
+ case 24: // 120
+ case 36: // 210
+ case 40: // 220
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 4: // 010
+ case 8: // 020
+ case 34: // 202
+ case 38: // 212
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 20: // 110
+ case 22: // 112
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (BOUNDARY_N(quad) && !START_HOLE_N(quad-1) &&
+ j % _y_chunk_size > 0 && j != _ny-1 && i % _x_chunk_size > 1)
+ _cache[quad] |= MASK_START_HOLE_N;
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 21: // 111
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ if (BOUNDARY_N(quad) && !START_HOLE_N(quad-1) &&
+ j % _y_chunk_size > 0 && j != _ny-1 && i % _x_chunk_size > 1)
+ _cache[quad] |= MASK_START_HOLE_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ }
+ }
+ else { // !_filled NW corner.
+ switch ((z_nw << 2) | (z_ne << 1) | z_sw) { // config
+ case 1: // 001
+ case 5: // 101
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 2: // 010
+ case 3: // 011
+ if (BOUNDARY_N(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_N;
+ start_in_row = true;
+ }
+ break;
+ case 4: // 100
+ case 6: // 110
+ if (BOUNDARY_W(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row = true;
+ }
+ break;
+ }
+ }
+ break;
+ case MASK_EXISTS_NE_CORNER:
+ if (_filled) {
+ switch ((z_nw << 4) | (z_ne << 2) | z_se) { // config
+ case 1: // 001
+ case 2: // 002
+ case 5: // 011
+ case 6: // 012
+ case 10: // 022
+ case 16: // 100
+ case 26: // 122
+ case 32: // 200
+ case 36: // 210
+ case 37: // 211
+ case 40: // 220
+ case 41: // 221
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 4: // 010
+ case 38: // 212
+ _cache[quad] |= MASK_START_N;
+ start_in_row = true;
+ break;
+ case 8: // 020
+ case 34: // 202
+ _cache[quad] |= MASK_START_E;
+ _cache[quad] |= MASK_START_N;
+ start_in_row = true;
+ break;
+ case 9: // 021
+ case 17: // 101
+ case 18: // 102
+ case 24: // 120
+ case 25: // 121
+ case 33: // 201
+ _cache[quad] |= MASK_START_CORNER;
+ _cache[quad] |= MASK_START_E;
+ start_in_row = true;
+ break;
+ case 20: // 110
+ case 21: // 111
+ case 22: // 112
+ if (BOUNDARY_N(quad) && !START_HOLE_N(quad-1) &&
+ j % _y_chunk_size > 0 && j != _ny-1 && i % _x_chunk_size > 1)
+ _cache[quad] |= MASK_START_HOLE_N;
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ }
+ }
+ else { // !_filled NE corner.
+ switch ((z_nw << 2) | (z_ne << 1) | z_se) { // config
+ case 1: // 001
+ if (BOUNDARY_E(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_E;
+ start_in_row = true;
+ }
+ break;
+ case 2: // 010
+ if (BOUNDARY_N(quad))
+ _cache[quad] |= MASK_START_BOUNDARY_N;
+ else if (!BOUNDARY_E(quad))
+ _cache[quad] |= MASK_START_N;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 3: // 011
+ if (BOUNDARY_N(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_N;
+ start_in_row = true;
+ }
+ break;
+ case 4: // 100
+ case 6: // 110
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 5: // 101
+ if (BOUNDARY_E(quad))
+ _cache[quad] |= MASK_START_BOUNDARY_E;
+ else if (!BOUNDARY_N(quad))
+ _cache[quad] |= MASK_START_E;
+ start_in_row |= ANY_START(quad);
+ break;
+ }
+ }
+ break;
+ case MASK_EXISTS_SW_CORNER:
+ if (_filled) {
+ switch ((z_nw << 4) | (z_sw << 2) | z_se) { // config
+ case 1: // 001
+ case 2: // 002
+ case 40: // 220
+ case 41: // 221
+ if (BOUNDARY_S(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_S;
+ start_in_row = true;
+ }
+ break;
+ case 4: // 010
+ case 5: // 011
+ case 6: // 012
+ case 8: // 020
+ case 9: // 021
+ case 18: // 102
+ case 24: // 120
+ case 33: // 201
+ case 34: // 202
+ case 36: // 210
+ case 37: // 211
+ case 38: // 212
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row |= ANY_START(quad);
+ break;
+ case 10: // 022
+ case 16: // 100
+ case 26: // 122
+ case 32: // 200
+ if (BOUNDARY_W(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row = true;
+ }
+ break;
+ case 17: // 101
+ case 25: // 121
+ if (BOUNDARY_S(quad)) _cache[quad] |= MASK_START_BOUNDARY_S;
+ if (BOUNDARY_W(quad)) _cache[quad] |= MASK_START_BOUNDARY_W;
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 20: // 110
+ case 21: // 111
+ case 22: // 112
+ if (BOUNDARY_S(quad))
+ _cache[quad] |= MASK_START_BOUNDARY_S;
+ else
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ }
+ }
+ else { // !_filled SW corner.
+ switch ((z_nw << 2) | (z_sw << 1) | z_se) { // config
+ case 1: // 001
+ case 3: // 011
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ case 2: // 010
+ case 6: // 110
+ if (BOUNDARY_S(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_S;
+ start_in_row = true;
+ }
+ break;
+ case 4: // 100
+ case 5: // 101
+ if (BOUNDARY_W(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_W;
+ start_in_row = true;
+ }
+ break;
+ }
+ }
+ break;
+ case MASK_EXISTS_SE_CORNER:
+ if (_filled) {
+ switch ((z_ne << 4) | (z_sw << 2) | z_se) { // config
+ case 1: // 001
+ case 2: // 002
+ case 4: // 010
+ case 5: // 011
+ case 6: // 012
+ case 8: // 020
+ case 9: // 021
+ case 17: // 101
+ case 18: // 102
+ case 20: // 110
+ case 21: // 111
+ case 22: // 112
+ case 24: // 120
+ case 25: // 121
+ case 33: // 201
+ case 34: // 202
+ case 36: // 210
+ case 37: // 211
+ case 38: // 212
+ case 40: // 220
+ case 41: // 221
+ if (BOUNDARY_S(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_S;
+ start_in_row = true;
+ }
+ break;
+ case 10: // 022
+ case 16: // 100
+ case 26: // 122
+ case 32: // 200
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ }
+ }
+ else { // !_filled SE corner.
+ switch ((z_ne << 2) | (z_sw << 1) | z_se) { // config
+ case 1: // 001
+ case 3: // 011
+ if (BOUNDARY_E(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_E;
+ start_in_row = true;
+ }
+ break;
+ case 2: // 010
+ case 6: // 110
+ if (BOUNDARY_S(quad)) {
+ _cache[quad] |= MASK_START_BOUNDARY_S;
+ start_in_row = true;
+ }
+ break;
+ case 4: // 100
+ case 5: // 101
+ _cache[quad] |= MASK_START_CORNER;
+ start_in_row = true;
+ break;
+ }
+ }
+ break;
+ }
+
+ z_nw = z_ne;
+ z_sw = z_se;
+ } // i-loop.
+
+ if (start_in_row)
+ j_final_start = j;
+ else if (j > 0)
+ _cache[chunk_istart + j*_nx] |= MASK_NO_STARTS_IN_ROW;
+ } // j-loop.
+
+ if (j_final_start < jend)
+ _cache[chunk_istart + (j_final_start+1)*_nx] |= MASK_NO_MORE_STARTS;
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::interp(
+ index_t point0, index_t point1, bool is_upper, double*& points) const
+{
+ auto frac = get_interp_fraction(
+ get_point_z(point0), get_point_z(point1), is_upper ? _upper_level : _lower_level);
+
+ assert(frac >= 0.0 && frac <= 1.0 && "Interp fraction out of bounds");
+
+ *points++ = get_point_x(point0)*frac + get_point_x(point1)*(1.0 - frac);
+ *points++ = get_point_y(point0)*frac + get_point_y(point1)*(1.0 - frac);
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::interp(
+ index_t point0, double x1, double y1, double z1, bool is_upper, double*& points) const
+{
+ auto frac = get_interp_fraction(
+ get_point_z(point0), z1, is_upper ? _upper_level : _lower_level);
+
+ assert(frac >= 0.0 && frac <= 1.0 && "Interp fraction out of bounds");
+
+ *points++ = get_point_x(point0)*frac + x1*(1.0 - frac);
+ *points++ = get_point_y(point0)*frac + y1*(1.0 - frac);
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::is_filled() const
+{
+ return _filled;
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::is_point_in_chunk(index_t point, const ChunkLocal& local) const
+{
+ return is_quad_in_bounds(point, local.istart-1, local.iend, local.jstart-1, local.jend);
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::is_quad_in_bounds(
+ index_t quad, index_t istart, index_t iend, index_t jstart, index_t jend) const
+{
+ return (quad % _nx >= istart && quad % _nx <= iend &&
+ quad / _nx >= jstart && quad / _nx <= jend);
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::is_quad_in_chunk(index_t quad, const ChunkLocal& local) const
+{
+ return is_quad_in_bounds(quad, local.istart, local.iend, local.jstart, local.jend);
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::line(const Location& start_location, ChunkLocal& local)
+{
+ // start_location.on_boundary indicates starts (and therefore also finishes)
+
+ assert(is_quad_in_chunk(start_location.quad, local));
+
+ Location location = start_location;
+ count_t point_count = 0;
+
+ // finished == true indicates closed line loop.
+ bool finished = follow_interior(location, start_location, local, point_count);
+
+ if (local.pass > 0) {
+ assert(local.line_offsets.current == local.line_offsets.start + local.line_count);
+ *local.line_offsets.current++ = local.total_point_count;
+ }
+
+ if (local.pass == 0 && !start_location.on_boundary && !finished)
+ // An internal start that isn't a line loop is part of a line strip that starts on a
+ // boundary and will be traced later. Do not count it as a valid start in pass 0 and remove
+ // the first point or it will be duplicated by the correct boundary-started line later.
+ point_count--;
+ else
+ local.line_count++;
+
+ local.total_point_count += point_count;
+}
+
+template <typename Derived>
+py::sequence BaseContourGenerator<Derived>::lines(double level)
+{
+ _filled = false;
+ _lower_level = _upper_level = level;
+
+ _identify_holes = false;
+ _output_chunked = !(_line_type == LineType::Separate || _line_type == LineType::SeparateCode);
+ _direct_points = _output_chunked;
+ _direct_line_offsets = (_line_type == LineType::ChunkCombinedOffset);
+ _direct_outer_offsets = false;
+ _outer_offsets_into_points = false;
+ _return_list_count = (_line_type == LineType::Separate) ? 1 : 2;
+
+ return march_wrapper();
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::march_chunk(
+ ChunkLocal& local, std::vector<py::list>& return_lists)
+{
+ for (local.pass = 0; local.pass < 2; ++local.pass) {
+ bool ignore_holes = (_identify_holes && local.pass == 1);
+
+ index_t j_final_start = local.jstart;
+ for (index_t j = local.jstart; j <= local.jend; ++j) {
+ index_t quad = local.istart + j*_nx;
+
+ if (NO_MORE_STARTS(quad))
+ break;
+
+ if (NO_STARTS_IN_ROW(quad))
+ continue;
+
+ // Want to count number of starts in this row, so store how many starts at start of row.
+ auto prev_start_count =
+ (_identify_holes ? local.line_count - local.hole_count : local.line_count);
+
+ for (index_t i = local.istart; i <= local.iend; ++i, ++quad) {
+ if (!ANY_START(quad))
+ continue;
+
+ assert(EXISTS_ANY(quad));
+
+ if (_filled) {
+ if (START_BOUNDARY_S(quad))
+ closed_line_wrapper(Location(quad, 1, _nx, Z_SW == 2, true), Outer, local);
+
+ if (START_BOUNDARY_W(quad))
+ closed_line_wrapper(Location(quad, -_nx, 1, Z_NW == 2, true), Outer, local);
+
+ if (START_CORNER(quad)) {
+ switch (EXISTS_ANY_CORNER(quad)) {
+ case MASK_EXISTS_NE_CORNER:
+ closed_line_wrapper(
+ Location(quad, -_nx+1, _nx+1, Z_NW == 2, true), Outer, local);
+ break;
+ case MASK_EXISTS_NW_CORNER:
+ closed_line_wrapper(
+ Location(quad, _nx+1, _nx-1, Z_SW == 2, true), Outer, local);
+ break;
+ case MASK_EXISTS_SE_CORNER:
+ closed_line_wrapper(
+ Location(quad, -_nx-1, -_nx+1, Z_NE == 2, true), Outer, local);
+ break;
+ default:
+ assert(EXISTS_SW_CORNER(quad));
+ if (!ignore_holes)
+ closed_line_wrapper(
+ Location(quad, _nx-1, -_nx-1, false, true), Hole, local);
+ break;
+ }
+ }
+
+ if (START_N(quad))
+ closed_line_wrapper(Location(quad, -_nx, 1, Z_NW > 0, false), Outer, local);
+
+ if (ignore_holes)
+ continue;
+
+ if (START_E(quad))
+ closed_line_wrapper(Location(quad, -1, -_nx, Z_NE > 0, false), Hole, local);
+
+ if (START_HOLE_N(quad))
+ closed_line_wrapper(Location(quad, -1, -_nx, false, true), Hole, local);
+ }
+ else { // !_filled
+ if (START_BOUNDARY_S(quad))
+ line(Location(quad, _nx, -1, false, true), local);
+
+ if (START_BOUNDARY_W(quad))
+ line(Location(quad, 1, _nx, false, true), local);
+
+ if (START_BOUNDARY_E(quad))
+ line(Location(quad, -1, -_nx, false, true), local);
+
+ if (START_BOUNDARY_N(quad))
+ line(Location(quad, -_nx, 1, false, true), local);
+
+ if (START_E(quad))
+ line(Location(quad, -1, -_nx, false, false), local);
+
+ if (START_N(quad))
+ line(Location(quad, -_nx, 1, false, false), local);
+
+ if (START_CORNER(quad)) {
+ index_t forward, left;
+ switch (EXISTS_ANY_CORNER(quad)) {
+ case MASK_EXISTS_NE_CORNER:
+ forward = _nx+1;
+ left = _nx-1;
+ break;
+ case MASK_EXISTS_NW_CORNER:
+ forward = _nx-1;
+ left = -_nx-1;
+ break;
+ case MASK_EXISTS_SE_CORNER:
+ forward = -_nx+1;
+ left = _nx+1;
+ break;
+ default:
+ assert(EXISTS_SW_CORNER(quad));
+ forward = -_nx-1;
+ left = -_nx+1;
+ break;
+ }
+ line(Location(quad, forward, left, false, true), local);
+ }
+ } // _filled
+ } // i
+
+ // Number of starts at end of row.
+ auto start_count =
+ (_identify_holes ? local.line_count - local.hole_count : local.line_count);
+ if (start_count > prev_start_count)
+ j_final_start = j;
+ else
+ _cache[local.istart + j*_nx] |= MASK_NO_STARTS_IN_ROW;
+ } // j
+
+ if (j_final_start < local.jend)
+ _cache[local.istart + (j_final_start+1)*_nx] |= MASK_NO_MORE_STARTS;
+
+ if (local.pass == 0) {
+ if (local.total_point_count == 0) {
+ local.points.clear();
+ local.line_offsets.clear();
+ local.outer_offsets.clear();
+ break; // Do not need pass 1.
+ }
+
+ // Create arrays for points, line_offsets and optionally outer_offsets. Arrays may be
+ // either C++ vectors or Python NumPy arrays. Want to group creation of the latter as
+ // threaded code needs to lock creation of these to limit access to a single thread.
+ if (_direct_points || _direct_line_offsets || _direct_outer_offsets) {
+ typename Derived::Lock lock(static_cast<Derived&>(*this));
+
+ // Strictly speaking adding the NumPy arrays to return_lists does not need to be
+ // within the lock.
+ if (_direct_points) {
+ return_lists[0][local.chunk] =
+ local.points.create_python(local.total_point_count, 2);
+ }
+ if (_direct_line_offsets) {
+ return_lists[1][local.chunk] =
+ local.line_offsets.create_python(local.line_count + 1);
+ }
+ if (_direct_outer_offsets) {
+ return_lists[2][local.chunk] =
+ local.outer_offsets.create_python(local.line_count - local.hole_count + 1);
+ }
+ }
+
+ if (!_direct_points)
+ local.points.create_cpp(2*local.total_point_count);
+
+ if (!_direct_line_offsets)
+ local.line_offsets.create_cpp(local.line_count + 1);
+
+ if (!_direct_outer_offsets) {
+ if (_identify_holes)
+ local.outer_offsets.create_cpp( local.line_count - local.hole_count + 1);
+ else
+ local.outer_offsets.clear();
+ }
+
+ // Reset counts for pass 1.
+ local.total_point_count = 0;
+ local.line_count = 0;
+ local.hole_count = 0;
+ }
+ } // pass
+
+ // Set final line and outer offsets.
+ if (local.line_count > 0) {
+ *local.line_offsets.current++ = local.total_point_count;
+
+ if (_identify_holes) {
+ if (_outer_offsets_into_points)
+ *local.outer_offsets.current++ = local.total_point_count;
+ else
+ *local.outer_offsets.current++ = local.line_count;
+ }
+ }
+
+ // Throw exception if the two passes returned different number of points, lines, etc.
+ check_consistent_counts(local);
+
+ if (local.total_point_count == 0) {
+ if (_output_chunked) {
+ typename Derived::Lock lock(static_cast<Derived&>(*this));
+ for (auto& list : return_lists)
+ list[local.chunk] = py::none();
+ }
+ }
+ else if (_filled)
+ static_cast<Derived*>(this)->export_filled(local, return_lists);
+ else
+ static_cast<Derived*>(this)->export_lines(local, return_lists);
+}
+
+template <typename Derived>
+py::sequence BaseContourGenerator<Derived>::march_wrapper()
+{
+ index_t list_len = _n_chunks;
+ if ((_filled && (_fill_type == FillType::OuterCode|| _fill_type == FillType::OuterOffset)) ||
+ (!_filled && (_line_type == LineType::Separate || _line_type == LineType::SeparateCode)))
+ list_len = 0;
+
+ // Prepare lists to return to python.
+ std::vector<py::list> return_lists;
+ return_lists.reserve(_return_list_count);
+ for (decltype(_return_list_count) i = 0; i < _return_list_count; ++i)
+ return_lists.emplace_back(list_len);
+
+ static_cast<Derived*>(this)->march(return_lists);
+
+ // Return to python objects.
+ if (_return_list_count == 1) {
+ assert(!_filled && _line_type == LineType::Separate);
+ return return_lists[0];
+ }
+ else if (_return_list_count == 2)
+ return py::make_tuple(return_lists[0], return_lists[1]);
+ else {
+ assert(_return_list_count == 3);
+ return py::make_tuple(return_lists[0], return_lists[1], return_lists[2]);
+ }
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::move_to_next_boundary_edge(
+ index_t& quad, index_t& forward, index_t& left) const
+{
+ // edge == 0 for E edge (facing N), forward = +_nx
+ // 2 for S edge (facing E), forward = +1
+ // 4 for W edge (facing S), forward = -_nx
+ // 6 for N edge (facing W), forward = -1
+ // 1 for SE edge (NW corner) from SW facing NE, forward = +_nx+1
+ // 3 for SW edge (NE corner) from NW facing SE, forward = -_nx+1
+ // 5 for NW edge (SE corner) from NE facing SW, forward = -_nx-1
+ // 7 for NE edge (SW corner) from SE facing NW, forward = +_nx-1
+ int edge = 0;
+
+ // Need index of quad that is the same as the end point, i.e. quad to SW of end point, as it is
+ // this point which we need to find the next available boundary of, looking clockwise.
+ if (forward > 0) {
+ if (forward == _nx) {
+ assert(left == -1);
+ // W edge facing N, no change to quad or edge.
+ }
+ else if (left == _nx) {
+ assert(forward == 1);
+ quad -= _nx; // S edge facing E.
+ edge = 2;
+ }
+ else if (EXISTS_SW_CORNER(quad)) {
+ assert(forward == _nx-1 && left == -_nx-1);
+ quad -= 1;
+ edge = 7;
+ }
+ else {
+ assert(EXISTS_NW_CORNER(quad) && forward == _nx+1 && _nx-1);
+ // quad unchanged.
+ edge = 1;
+ }
+ }
+ else { // forward < 0
+ if (forward == -_nx) {
+ assert(left == 1);
+ quad -= _nx+1; // W edge facing S.
+ edge = 4;
+ }
+ else if (left == -_nx) {
+ assert(forward == -1);
+ quad -= 1; // N edge facing W.
+ edge = 6;
+ }
+ else if (EXISTS_NE_CORNER(quad)) {
+ assert(forward == -_nx+1 && left == _nx+1);
+ quad -= _nx;
+ edge = 3;
+ }
+ else {
+ assert(EXISTS_SE_CORNER(quad) && forward == -_nx-1 && left == -_nx+1);
+ quad -= _nx+1;
+ edge = 5;
+ }
+ }
+
+ // If _corner_mask not set, only need to consider odd edge in loop below.
+ if (!_corner_mask)
+ ++edge;
+
+ while (true) {
+ // Look at possible edges that leave NE point of quad.
+ // If something is wrong here or in the setup of the boundary flags, can end up with an
+ // infinite loop!
+ switch (edge) {
+ case 0:
+ // Is there an edge to follow towards SW?
+ if (EXISTS_SE_CORNER(quad)) { // Equivalent to BOUNDARY_NE.
+ // quad unchanged.
+ forward = -_nx-1;
+ left = -_nx+1;
+ return;
+ }
+ break;
+ case 1:
+ // Is there an edge to follow towards W?
+ if (BOUNDARY_N(quad)) {
+ // quad unchanged.
+ forward = -1;
+ left = -_nx;
+ return;
+ }
+ break;
+ case 2:
+ // Is there an edge to follow towards NW?
+ if (EXISTS_SW_CORNER(quad+_nx)) { // Equivalent to BOUNDARY_NE.
+ quad += _nx;
+ forward = _nx-1;
+ left = -_nx-1;
+ return;
+ }
+ break;
+ case 3:
+ // Is there an edge to follow towards N?
+ if (BOUNDARY_E(quad+_nx)) { // Really a BOUNDARY_W check.
+ quad += _nx;
+ forward = _nx;
+ left = -1;
+ return;
+ }
+ break;
+ case 4:
+ // Is there an edge to follow towards NE?
+ if (EXISTS_NW_CORNER(quad+_nx+1)) { // Equivalent to BOUNDARY_SE.
+ quad += _nx+1;
+ forward = _nx+1;
+ left = _nx-1;
+ return;
+ }
+ break;
+ case 5:
+ // Is there an edge to follow towards E?
+ if (BOUNDARY_N(quad+1)) { // Really a BOUNDARY_S check
+ quad += _nx+1;
+ forward = 1;
+ left = _nx;
+ return;
+ }
+ break;
+ case 6:
+ // Is there an edge to follow towards SE?
+ if (EXISTS_NE_CORNER(quad+1)) { // Equivalent to BOUNDARY_SW.
+ quad += 1;
+ forward = -_nx+1;
+ left = _nx+1;
+ return;
+ }
+ break;
+ case 7:
+ // Is there an edge to follow towards S?
+ if (BOUNDARY_E(quad)) {
+ quad += 1;
+ forward = -_nx;
+ left = 1;
+ return;
+ }
+ break;
+ default:
+ assert(0 && "Invalid edge index");
+ break;
+ }
+
+ edge = _corner_mask ? (edge + 1) % 8 : (edge + 2) % 8;
+ }
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::set_look_flags(index_t hole_start_quad)
+{
+ assert(_identify_holes);
+
+ // The only possible hole starts are START_E (from E to N), START_HOLE_N (on N boundary, E to W)
+ // and START_CORNER for SW corner (on boundary, SE to NW).
+ assert(hole_start_quad >= 0 && hole_start_quad < _n);
+ assert(EXISTS_N_EDGE(hole_start_quad) || EXISTS_SW_CORNER(hole_start_quad));
+ assert(!LOOK_S(hole_start_quad) && "Look S already set");
+
+ _cache[hole_start_quad] |= MASK_LOOK_S;
+
+ // Walk S until find place to mark corresponding look N.
+ auto quad = hole_start_quad;
+
+ while (true) {
+ assert(quad >= 0 && quad < _n);
+ assert(EXISTS_N_EDGE(quad) || (quad == hole_start_quad && EXISTS_SW_CORNER(quad)));
+
+ if (BOUNDARY_S(quad) || EXISTS_NE_CORNER(quad) || EXISTS_NW_CORNER(quad) || Z_SE != 1) {
+ assert(!LOOK_N(quad) && "Look N already set");
+ _cache[quad] |= MASK_LOOK_N;
+ break;
+ }
+
+ quad -= _nx;
+ }
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::supports_fill_type(FillType fill_type)
+{
+ switch (fill_type) {
+ case FillType::OuterCode:
+ case FillType::OuterOffset:
+ case FillType::ChunkCombinedCode:
+ case FillType::ChunkCombinedOffset:
+ case FillType::ChunkCombinedCodeOffset:
+ case FillType::ChunkCombinedOffsetOffset:
+ return true;
+ default:
+ return false;
+ }
+}
+
+template <typename Derived>
+bool BaseContourGenerator<Derived>::supports_line_type(LineType line_type)
+{
+ switch (line_type) {
+ case LineType::Separate:
+ case LineType::SeparateCode:
+ case LineType::ChunkCombinedCode:
+ case LineType::ChunkCombinedOffset:
+ return true;
+ default:
+ return false;
+ }
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::write_cache() const
+{
+ std::cout << "---------- Cache ----------" << std::endl;
+ index_t ny = _n / _nx;
+ for (index_t j = ny-1; j >= 0; --j) {
+ std::cout << "j=" << j << " ";
+ for (index_t i = 0; i < _nx; ++i) {
+ index_t quad = i + j*_nx;
+ write_cache_quad(quad);
+ }
+ std::cout << std::endl;
+ }
+ std::cout << " ";
+ for (index_t i = 0; i < _nx; ++i)
+ std::cout << "i=" << i << " ";
+ std::cout << std::endl;
+ std::cout << "---------------------------" << std::endl;
+}
+
+template <typename Derived>
+void BaseContourGenerator<Derived>::write_cache_quad(index_t quad) const
+{
+ assert(quad >= 0 && quad < _n && "quad index out of bounds");
+ std::cout << (NO_MORE_STARTS(quad) ? 'x' :
+ (NO_STARTS_IN_ROW(quad) ? 'i' : '.'));
+ std::cout << (EXISTS_QUAD(quad) ? "Q_" :
+ (EXISTS_NW_CORNER(quad) ? "NW" :
+ (EXISTS_NE_CORNER(quad) ? "NE" :
+ (EXISTS_SW_CORNER(quad) ? "SW" :
+ (EXISTS_SE_CORNER(quad) ? "SE" : "..")))));
+ std::cout << (BOUNDARY_N(quad) && BOUNDARY_E(quad) ? 'b' : (
+ BOUNDARY_N(quad) ? 'n' : (BOUNDARY_E(quad) ? 'e' : '.')));
+ std::cout << Z_LEVEL(quad);
+ std::cout << ((_cache[quad] & MASK_MIDDLE) >> 2);
+ std::cout << (START_BOUNDARY_S(quad) ? 's' : '.');
+ std::cout << (START_BOUNDARY_W(quad) ? 'w' : '.');
+ if (!_filled) {
+ std::cout << (START_BOUNDARY_E(quad) ? 'e' : '.');
+ std::cout << (START_BOUNDARY_N(quad) ? 'n' : '.');
+ }
+ std::cout << (START_E(quad) ? 'E' : '.');
+ std::cout << (START_N(quad) ? 'N' : '.');
+ if (_filled)
+ std::cout << (START_HOLE_N(quad) ? 'h' : '.');
+ std::cout << (START_CORNER(quad) ? 'c' : '.');
+ if (_filled)
+ std::cout << (LOOK_N(quad) && LOOK_S(quad) ? 'B' :
+ (LOOK_N(quad) ? '^' : (LOOK_S(quad) ? 'v' : '.')));
+ std::cout << ' ';
+}
+
+template <typename Derived>
+typename BaseContourGenerator<Derived>::ZLevel BaseContourGenerator<Derived>::z_to_zlevel(
+ double z_value) const
+{
+ return (_filled && z_value > _upper_level) ? 2 : (z_value > _lower_level ? 1 : 0);
+}
+
+} // namespace contourpy
+
+#endif // CONTOURPY_BASE_IMPL_H