123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- /*
- * Mpl2014ContourGenerator
- * -----------------------
- * A ContourGenerator generates contours for scalar fields defined on
- * quadrilateral grids. A single QuadContourGenerator object can create both
- * line contours (at single levels) and filled contours (between pairs of
- * levels) for the same field.
- *
- * A field to be contoured has nx, ny points in the x- and y-directions
- * respectively. The quad grid is defined by x and y arrays of shape(ny, nx),
- * and the field itself is the z array also of shape(ny, nx). There is an
- * optional boolean mask; if it exists then it also has shape(ny, nx). The
- * mask applies to grid points rather than quads.
- *
- * How quads are masked based on the point mask is determined by the boolean
- * 'corner_mask' flag. If false then any quad that has one or more of its four
- * corner points masked is itself masked. If true the behaviour is the same
- * except that any quad which has exactly one of its four corner points masked
- * has only the triangular corner (half of the quad) adjacent to that point
- * masked; the opposite triangular corner has three unmasked points and is not
- * masked.
- *
- * By default the entire domain of nx*ny points is contoured together which can
- * result in some very long polygons. The alternative is to break up the
- * domain into subdomains or 'chunks' of smaller size, each of which is
- * independently contoured. The size of these chunks is controlled by the
- * 'nchunk' (or 'chunk_size') parameter. Chunking not only results in shorter
- * polygons but also requires slightly less RAM. It can result in rendering
- * artifacts though, depending on backend, antialiased flag and alpha value.
- *
- * Notation
- * --------
- * i and j are array indices in the x- and y-directions respectively. Although
- * a single element of an array z can be accessed using z[j][i] or z(j,i), it
- * is often convenient to use the single quad index z[quad], where
- * quad = i + j*nx
- * and hence
- * i = quad % nx
- * j = quad / nx
- *
- * Rather than referring to x- and y-directions, compass directions are used
- * instead such that W, E, S, N refer to the -x, +x, -y, +y directions
- * respectively. To move one quad to the E you would therefore add 1 to the
- * quad index, to move one quad to the N you would add nx to the quad index.
- *
- * Cache
- * -----
- * Lots of information that is reused during contouring is stored as single
- * bits in a mesh-sized cache, indexed by quad. Each quad's cache entry stores
- * information about the quad itself such as if it is masked, and about the
- * point at the SW corner of the quad, and about the W and S edges. Hence
- * information about each point and each edge is only stored once in the cache.
- *
- * Cache information is divided into two types: that which is constant over the
- * lifetime of the QuadContourGenerator, and that which changes for each
- * contouring operation. The former is all grid-specific information such
- * as quad and corner masks, and which edges are boundaries, either between
- * masked and non-masked regions or between adjacent chunks. The latter
- * includes whether points lie above or below the current contour levels, plus
- * some flags to indicate how the contouring is progressing.
- *
- * Line Contours
- * -------------
- * A line contour connects points with the same z-value. Each point of such a
- * contour occurs on an edge of the grid, at a point linearly interpolated to
- * the contour z-level from the z-values at the end points of the edge. The
- * direction of a line contour is such that higher values are to the left of
- * the contour, so any edge that the contour passes through will have a left-
- * hand end point with z > contour level and a right-hand end point with
- * z <= contour level.
- *
- * Line contours are of two types. Firstly there are open line strips that
- * start on a boundary, traverse the interior of the domain and end on a
- * boundary. Secondly there are closed line loops that occur completely within
- * the interior of the domain and do not touch a boundary.
- *
- * The ContourGenerator makes two sweeps through the grid to generate line
- * contours for a particular level. In the first sweep it looks only for start
- * points that occur on boundaries, and when it finds one it follows the
- * contour through the interior until it finishes on another boundary edge.
- * Each quad that is visited by the algorithm has a 'visited' flag set in the
- * cache to indicate that the quad does not need to be visited again. In the
- * second sweep all non-visited quads are checked to see if they contain part
- * of an interior closed loop, and again each time one is found it is followed
- * through the domain interior until it returns back to its start quad and is
- * therefore completed.
- *
- * The situation is complicated by saddle quads that have two opposite corners
- * with z >= contour level and the other two corners with z < contour level.
- * These therefore contain two segments of a line contour, and the visited
- * flags take account of this by only being set on the second visit. On the
- * first visit a number of saddle flags are set in the cache to indicate which
- * one of the two segments has been completed so far.
- *
- * Filled Contours
- * ---------------
- * Filled contours are produced between two contour levels and are always
- * closed polygons. They can occur completely within the interior of the
- * domain without touching a boundary, following either the lower or upper
- * contour levels. Those on the lower level are exactly like interior line
- * contours with higher values on the left. Those on the upper level are
- * reversed such that higher values are on the right.
- *
- * Filled contours can also involve a boundary in which case they consist of
- * one or more sections along a boundary and one or more sections through the
- * interior. Interior sections can be on either level, and again those on the
- * upper level have higher values on the right. Boundary sections can remain
- * on either contour level or switch between the two.
- *
- * Once the start of a filled contour is found, the algorithm is similar to
- * that for line contours in that it follows the contour to its end, which
- * because filled contours are always closed polygons will be by returning
- * back to the start. However, because two levels must be considered, each
- * level has its own set of saddle and visited flags and indeed some extra
- * visited flags for boundary edges.
- *
- * The major complication for filled contours is that some polygons can be
- * holes (with points ordered clockwise) within other polygons (with points
- * ordered anticlockwise). When it comes to rendering filled contours each
- * non-hole polygon must be rendered along with its zero or more contained
- * holes or the rendering will not be correct. The filled contour finding
- * algorithm could progress pretty much as the line contour algorithm does,
- * taking each polygon as it is found, but then at the end there would have to
- * be an extra step to identify the parent non-hole polygon for each hole.
- * This is not a particularly onerous task but it does not scale well and can
- * easily dominate the execution time of the contour finding for even modest
- * problems. It is much better to identity each hole's parent non-hole during
- * the sweep algorithm.
- *
- * This requirement dictates the order that filled contours are identified. As
- * the algorithm sweeps up through the grid, every time a polygon passes
- * through a quad a ParentCache object is updated with the new possible parent.
- * When a new hole polygon is started, the ParentCache is used to find the
- * first possible parent in the same quad or to the S of it. Great care is
- * needed each time a new quad is checked to see if a new polygon should be
- * started, as a single quad can have multiple polygon starts, e.g. a quad
- * could be a saddle quad for both lower and upper contour levels, meaning it
- * has four contour line segments passing through it which could all be from
- * different polygons. The S-most polygon must be started first, then the next
- * S-most and so on until the N-most polygon is started in that quad.
- */
- #ifndef CONTOURPY_MPL_2014_H
- #define CONTOURPY_MPL_2014_H
- #include "contour_generator.h"
- #include <list>
- #include <iostream>
- #include <vector>
- namespace contourpy {
- namespace mpl2014 {
- // Edge of a quad including diagonal edges of masked quads if _corner_mask true.
- typedef enum
- {
- // Listing values here so easier to check for debug purposes.
- Edge_None = -1,
- Edge_E = 0,
- Edge_N = 1,
- Edge_W = 2,
- Edge_S = 3,
- // The following are only used if _corner_mask is true.
- Edge_NE = 4,
- Edge_NW = 5,
- Edge_SW = 6,
- Edge_SE = 7
- } Edge;
- // Combination of a quad and an edge of that quad.
- // An invalid quad edge has quad of -1.
- struct QuadEdge
- {
- QuadEdge() = delete;
- QuadEdge(index_t quad_, Edge edge_);
- bool operator==(const QuadEdge& other) const;
- friend std::ostream& operator<<(std::ostream& os, const QuadEdge& quad_edge);
- index_t quad;
- Edge edge;
- };
- // 2D point with x,y coordinates.
- struct XY
- {
- XY() = delete;
- XY(const double& x_, const double& y_);
- bool operator==(const XY& other) const;
- friend std::ostream& operator<<(std::ostream& os, const XY& xy);
- double x, y;
- };
- // A single line of a contour, which may be a closed line loop or an open line
- // strip. Identical adjacent points are avoided using push_back().
- // A ContourLine is either a hole (points ordered clockwise) or it is not
- // (points ordered anticlockwise). Each hole has a parent ContourLine that is
- // not a hole; each non-hole contains zero or more child holes. A non-hole and
- // its child holes must be rendered together to obtain the correct results.
- class ContourLine : public std::vector<XY>
- {
- public:
- typedef std::list<ContourLine*> Children;
- explicit ContourLine(bool is_hole);
- void add_child(ContourLine* child);
- void clear_parent();
- const Children& get_children() const;
- const ContourLine* get_parent() const;
- ContourLine* get_parent();
- bool is_hole() const;
- void set_parent(ContourLine* parent);
- void write() const;
- private:
- bool _is_hole;
- ContourLine* _parent; // Only set if is_hole, not owned.
- Children _children; // Only set if !is_hole, not owned.
- };
- // A Contour is a collection of zero or more ContourLines.
- class Contour : public std::vector<ContourLine*>
- {
- public:
- Contour();
- virtual ~Contour();
- void delete_contour_lines();
- void write() const;
- };
- // Single chunk of ContourLine parents, indexed by quad. As a chunk's filled
- // contours are created, the ParentCache is updated each time a ContourLine
- // passes through each quad. When a new ContourLine is created, if it is a
- // hole its parent ContourLine is read from the ParentCache by looking at the
- // start quad, then each quad to the S in turn until a non-zero ContourLine is
- // found.
- class ParentCache
- {
- public:
- ParentCache(index_t nx, index_t x_chunk_points, index_t y_chunk_points);
- ContourLine* get_parent(index_t quad);
- void set_chunk_starts(index_t istart, index_t jstart);
- void set_parent(index_t quad, ContourLine& contour_line);
- private:
- index_t index_to_index(index_t quad) const;
- index_t _nx;
- index_t _x_chunk_points, _y_chunk_points; // Number of points not quads.
- std::vector<ContourLine*> _lines; // Not owned.
- index_t _istart, _jstart;
- };
- // See overview of algorithm at top of file.
- class Mpl2014ContourGenerator : public ContourGenerator
- {
- public:
- // Constructor with optional mask.
- // x, y, z: double arrays of shape (ny,nx).
- // mask: boolean array, ether empty (if no mask), or of shape (ny,nx).
- // corner_mask: flag for different masking behaviour.
- // x_chunk_size: 0 for no chunking, or +ve integer for size of chunks that
- // the x-direction is subdivided into.
- // y_chunk_size: 0 for no chunking, or +ve integer for size of chunks that
- // the y-direction is subdivided into.
- Mpl2014ContourGenerator(
- const CoordinateArray& x, const CoordinateArray& y, const CoordinateArray& z,
- const MaskArray& mask, bool corner_mask, index_t x_chunk_size, index_t y_chunk_size);
- // Destructor.
- ~Mpl2014ContourGenerator();
- // Create and return polygons for a filled contour between the two
- // specified levels.
- py::tuple filled(const double& lower_level, const double& upper_level);
- py::tuple get_chunk_count() const; // Return (y_chunk_count, x_chunk_count)
- py::tuple get_chunk_size() const; // Return (y_chunk_size, x_chunk_size)
- bool get_corner_mask() const;
- // Create and return polygons for a line (i.e. non-filled) contour at the
- // specified level.
- py::tuple lines(const double& level);
- private:
- // Typedef for following either a boundary of the domain or the interior;
- // clearer than using a boolean.
- typedef enum
- {
- Boundary,
- Interior
- } BoundaryOrInterior;
- // Typedef for direction of movement from one quad to the next.
- typedef enum
- {
- Dir_Right = -1,
- Dir_Straight = 0,
- Dir_Left = +1
- } Dir;
- // Typedef for a polygon being a hole or not; clearer than using a boolean.
- typedef enum
- {
- NotHole,
- Hole
- } HoleOrNot;
- // Append a C++ ContourLine to the end of two python lists. Used for line
- // contours where each ContourLine is converted to a separate numpy array
- // of (x,y) points and a second numpy array of 'kinds' or 'codes'.
- // Clears the ContourLine too.
- void append_contour_line_to_vertices_and_codes(
- ContourLine& contour_line, py::list& vertices_list, py::list& codes_list) const;
- // Append a C++ Contour to the end of two python lists. Used for filled
- // contours where each non-hole ContourLine and its child holes are
- // represented by a numpy array of (x,y) points and a second numpy array of
- // 'kinds' or 'codes' that indicates where the points array is split into
- // individual polygons.
- // Clears the Contour too, freeing each ContourLine as soon as possible
- // for minimum RAM usage.
- void append_contour_to_vertices_and_codes(
- Contour& contour, py::list& vertices_list, py::list& codes_list) const;
- // Return number of chunks that fit in the specified point_count.
- static index_t calc_chunk_count(index_t point_count, index_t chunk_size);
- // Return actual chunk_size from specified point_count and requested chunk_size.
- static index_t calc_chunk_size(index_t point_count, index_t chunk_size);
- // Append the point on the specified QuadEdge that intersects the specified
- // level to the specified ContourLine.
- void edge_interp(const QuadEdge& quad_edge, const double& level, ContourLine& contour_line);
- // Follow a contour along a boundary, appending points to the ContourLine
- // as it progresses. Only called for filled contours. Stops when the
- // contour leaves the boundary to move into the interior of the domain, or
- // when the start_quad_edge is reached in which case the ContourLine is a
- // completed closed loop. Always adds the end point of each boundary edge
- // to the ContourLine, regardless of whether moving to another boundary
- // edge or leaving the boundary into the interior. Never adds the start
- // point of the first boundary edge to the ContourLine.
- // contour_line: ContourLine to append points to.
- // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge
- // that is stopped on.
- // lower_level: lower contour z-value.
- // upper_level: upper contour z-value.
- // level_index: level index started on (1 = lower, 2 = upper level).
- // start_quad_edge: QuadEdge that the ContourLine started from, which is
- // used to check if the ContourLine is finished.
- // Returns the end level_index.
- unsigned int follow_boundary(
- ContourLine& contour_line, QuadEdge& quad_edge, const double& lower_level,
- const double& upper_level, unsigned int level_index, const QuadEdge& start_quad_edge);
- // Follow a contour across the interior of the domain, appending points to
- // the ContourLine as it progresses. Called for both line and filled
- // contours. Stops when the contour reaches a boundary or, if the
- // start_quad_edge is specified, when quad_edge == start_quad_edge and
- // level_index == start_level_index. Always adds the end point of each
- // quad traversed to the ContourLine; only adds the start point of the
- // first quad if want_initial_point flag is true.
- // contour_line: ContourLine to append points to.
- // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge
- // that is stopped on.
- // level_index: level index started on (1 = lower, 2 = upper level).
- // level: contour z-value.
- // want_initial_point: whether want to append the initial point to the
- // ContourLine or not.
- // start_quad_edge: the QuadEdge that the ContourLine started from to
- // check if the ContourLine is finished, or 0 if no check should occur.
- // start_level_index: the level_index that the ContourLine started from.
- // set_parents: whether should set ParentCache as it progresses or not.
- // This is true for filled contours, false for line contours.
- void follow_interior(
- ContourLine& contour_line, QuadEdge& quad_edge, unsigned int level_index,
- const double& level, bool want_initial_point, const QuadEdge* start_quad_edge,
- unsigned int start_level_index, bool set_parents);
- // Return the index limits of a particular chunk.
- void get_chunk_limits(
- index_t ijchunk, index_t& ichunk, index_t& jchunk, index_t& istart, index_t& iend,
- index_t& jstart, index_t& jend);
- // Check if a contour starts within the specified corner quad on the
- // specified level_index, and if so return the start edge. Otherwise
- // return Edge_None.
- Edge get_corner_start_edge(index_t quad, unsigned int level_index) const;
- // Return index of point at start or end of specified QuadEdge, assuming
- // anticlockwise ordering around non-masked quads.
- index_t get_edge_point_index(const QuadEdge& quad_edge, bool start) const;
- // Return the edge to exit a quad from, given the specified entry quad_edge
- // and direction to move in.
- Edge get_exit_edge(const QuadEdge& quad_edge, Dir dir) const;
- const double& get_point_x(index_t point) const;
- const double& get_point_y(index_t point) const;
- // Append the (x,y) coordinates of the specified point index to the
- // specified ContourLine.
- void get_point_xy(index_t point, ContourLine& contour_line) const;
- // Return the z-value of the specified point index.
- const double& get_point_z(index_t point) const;
- // Check if a contour starts within the specified non-corner quad on the
- // specified level_index, and if so return the start edge. Otherwise
- // return Edge_None.
- Edge get_quad_start_edge(index_t quad, unsigned int level_index) const;
- // Check if a contour starts within the specified quad, whether it is a
- // corner or a full quad, and if so return the start edge. Otherwise
- // return Edge_None.
- Edge get_start_edge(index_t quad, unsigned int level_index) const;
- // Initialise the cache to contain grid information that is constant
- // across the lifetime of this object, i.e. does not vary between calls to
- // create_contour() and create_filled_contour().
- void init_cache_grid(const MaskArray& mask);
- // Initialise the cache with information that is specific to contouring the
- // specified two levels. The levels are the same for contour lines,
- // different for filled contours.
- void init_cache_levels(const double& lower_level, const double& upper_level);
- // Append the (x,y) point at which the level intersects the line connecting
- // the two specified point indices to the specified ContourLine.
- void interp(
- index_t point1, index_t point2, const double& level, ContourLine& contour_line) const;
- // Return true if the specified QuadEdge is a boundary, i.e. is either an
- // edge between a masked and non-masked quad/corner or is a chunk boundary.
- bool is_edge_a_boundary(const QuadEdge& quad_edge) const;
- // Follow a boundary from one QuadEdge to the next in an anticlockwise
- // manner around the non-masked region.
- void move_to_next_boundary_edge(QuadEdge& quad_edge) const;
- // Move from the quad specified by quad_edge.quad to the neighbouring quad
- // by crossing the edge specified by quad_edge.edge.
- void move_to_next_quad(QuadEdge& quad_edge) const;
- // Check for filled contours starting within the specified quad and
- // complete any that are found, appending them to the specified Contour.
- void single_quad_filled(
- Contour& contour, index_t quad, const double& lower_level, const double& upper_level);
- // Start and complete a filled contour line.
- // quad: index of quad to start ContourLine in.
- // edge: edge of quad to start ContourLine from.
- // start_level_index: the level_index that the ContourLine starts from.
- // hole_or_not: whether the ContourLine is a hole or not.
- // boundary_or_interior: whether the ContourLine starts on a boundary or
- // the interior.
- // lower_level: lower contour z-value.
- // upper_level: upper contour z-value.
- // Returns newly created ContourLine.
- ContourLine* start_filled(
- index_t quad, Edge edge, unsigned int start_level_index, HoleOrNot hole_or_not,
- BoundaryOrInterior boundary_or_interior, const double& lower_level,
- const double& upper_level);
- // Start and complete a line contour that both starts and end on a
- // boundary, traversing the interior of the domain.
- // vertices_list: Python list that the ContourLine should be appended to.
- // codes_list: Python list that the kind codes should be appended to.
- // quad: index of quad to start ContourLine in.
- // edge: boundary edge to start ContourLine from.
- // level: contour z-value.
- // Returns true if the start quad does not need to be visited again, i.e.
- // VISITED(quad,1).
- bool start_line(
- py::list& vertices_list, py::list& codes_list, index_t quad, Edge edge,
- const double& level);
- // Debug function that writes the cache status to stdout.
- //void write_cache(bool grid_only = false) const;
- // Debug function that writes that cache status for a single quad to
- // stdout.
- //void write_cache_quad(index_t quad, bool grid_only) const;
- // Note that mask is not stored as once it has been used to initialise the
- // cache it is no longer needed.
- CoordinateArray _x, _y, _z;
- index_t _nx, _ny; // Number of points in each direction.
- index_t _n; // Total number of points (and hence quads).
- bool _corner_mask;
- index_t _x_chunk_size; // Number of quads per chunk (not points).
- index_t _y_chunk_size; // Always > 0.
- index_t _nxchunk, _nychunk; // Number of chunks in each direction.
- index_t _chunk_count; // Total number of chunks.
- typedef uint32_t CacheItem;
- CacheItem* _cache;
- ParentCache _parent_cache; // On W quad sides.
- };
- } // namespace mpl2014
- } // namespace contourpy
- #endif // #define CONTOURPY_MPL_2014_H
|