#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("chunk_sizes 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>
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>
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>
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>::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_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
