mpl2014.h 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. /*
  2. * Mpl2014ContourGenerator
  3. * -----------------------
  4. * A ContourGenerator generates contours for scalar fields defined on
  5. * quadrilateral grids. A single QuadContourGenerator object can create both
  6. * line contours (at single levels) and filled contours (between pairs of
  7. * levels) for the same field.
  8. *
  9. * A field to be contoured has nx, ny points in the x- and y-directions
  10. * respectively. The quad grid is defined by x and y arrays of shape(ny, nx),
  11. * and the field itself is the z array also of shape(ny, nx). There is an
  12. * optional boolean mask; if it exists then it also has shape(ny, nx). The
  13. * mask applies to grid points rather than quads.
  14. *
  15. * How quads are masked based on the point mask is determined by the boolean
  16. * 'corner_mask' flag. If false then any quad that has one or more of its four
  17. * corner points masked is itself masked. If true the behaviour is the same
  18. * except that any quad which has exactly one of its four corner points masked
  19. * has only the triangular corner (half of the quad) adjacent to that point
  20. * masked; the opposite triangular corner has three unmasked points and is not
  21. * masked.
  22. *
  23. * By default the entire domain of nx*ny points is contoured together which can
  24. * result in some very long polygons. The alternative is to break up the
  25. * domain into subdomains or 'chunks' of smaller size, each of which is
  26. * independently contoured. The size of these chunks is controlled by the
  27. * 'nchunk' (or 'chunk_size') parameter. Chunking not only results in shorter
  28. * polygons but also requires slightly less RAM. It can result in rendering
  29. * artifacts though, depending on backend, antialiased flag and alpha value.
  30. *
  31. * Notation
  32. * --------
  33. * i and j are array indices in the x- and y-directions respectively. Although
  34. * a single element of an array z can be accessed using z[j][i] or z(j,i), it
  35. * is often convenient to use the single quad index z[quad], where
  36. * quad = i + j*nx
  37. * and hence
  38. * i = quad % nx
  39. * j = quad / nx
  40. *
  41. * Rather than referring to x- and y-directions, compass directions are used
  42. * instead such that W, E, S, N refer to the -x, +x, -y, +y directions
  43. * respectively. To move one quad to the E you would therefore add 1 to the
  44. * quad index, to move one quad to the N you would add nx to the quad index.
  45. *
  46. * Cache
  47. * -----
  48. * Lots of information that is reused during contouring is stored as single
  49. * bits in a mesh-sized cache, indexed by quad. Each quad's cache entry stores
  50. * information about the quad itself such as if it is masked, and about the
  51. * point at the SW corner of the quad, and about the W and S edges. Hence
  52. * information about each point and each edge is only stored once in the cache.
  53. *
  54. * Cache information is divided into two types: that which is constant over the
  55. * lifetime of the QuadContourGenerator, and that which changes for each
  56. * contouring operation. The former is all grid-specific information such
  57. * as quad and corner masks, and which edges are boundaries, either between
  58. * masked and non-masked regions or between adjacent chunks. The latter
  59. * includes whether points lie above or below the current contour levels, plus
  60. * some flags to indicate how the contouring is progressing.
  61. *
  62. * Line Contours
  63. * -------------
  64. * A line contour connects points with the same z-value. Each point of such a
  65. * contour occurs on an edge of the grid, at a point linearly interpolated to
  66. * the contour z-level from the z-values at the end points of the edge. The
  67. * direction of a line contour is such that higher values are to the left of
  68. * the contour, so any edge that the contour passes through will have a left-
  69. * hand end point with z > contour level and a right-hand end point with
  70. * z <= contour level.
  71. *
  72. * Line contours are of two types. Firstly there are open line strips that
  73. * start on a boundary, traverse the interior of the domain and end on a
  74. * boundary. Secondly there are closed line loops that occur completely within
  75. * the interior of the domain and do not touch a boundary.
  76. *
  77. * The ContourGenerator makes two sweeps through the grid to generate line
  78. * contours for a particular level. In the first sweep it looks only for start
  79. * points that occur on boundaries, and when it finds one it follows the
  80. * contour through the interior until it finishes on another boundary edge.
  81. * Each quad that is visited by the algorithm has a 'visited' flag set in the
  82. * cache to indicate that the quad does not need to be visited again. In the
  83. * second sweep all non-visited quads are checked to see if they contain part
  84. * of an interior closed loop, and again each time one is found it is followed
  85. * through the domain interior until it returns back to its start quad and is
  86. * therefore completed.
  87. *
  88. * The situation is complicated by saddle quads that have two opposite corners
  89. * with z >= contour level and the other two corners with z < contour level.
  90. * These therefore contain two segments of a line contour, and the visited
  91. * flags take account of this by only being set on the second visit. On the
  92. * first visit a number of saddle flags are set in the cache to indicate which
  93. * one of the two segments has been completed so far.
  94. *
  95. * Filled Contours
  96. * ---------------
  97. * Filled contours are produced between two contour levels and are always
  98. * closed polygons. They can occur completely within the interior of the
  99. * domain without touching a boundary, following either the lower or upper
  100. * contour levels. Those on the lower level are exactly like interior line
  101. * contours with higher values on the left. Those on the upper level are
  102. * reversed such that higher values are on the right.
  103. *
  104. * Filled contours can also involve a boundary in which case they consist of
  105. * one or more sections along a boundary and one or more sections through the
  106. * interior. Interior sections can be on either level, and again those on the
  107. * upper level have higher values on the right. Boundary sections can remain
  108. * on either contour level or switch between the two.
  109. *
  110. * Once the start of a filled contour is found, the algorithm is similar to
  111. * that for line contours in that it follows the contour to its end, which
  112. * because filled contours are always closed polygons will be by returning
  113. * back to the start. However, because two levels must be considered, each
  114. * level has its own set of saddle and visited flags and indeed some extra
  115. * visited flags for boundary edges.
  116. *
  117. * The major complication for filled contours is that some polygons can be
  118. * holes (with points ordered clockwise) within other polygons (with points
  119. * ordered anticlockwise). When it comes to rendering filled contours each
  120. * non-hole polygon must be rendered along with its zero or more contained
  121. * holes or the rendering will not be correct. The filled contour finding
  122. * algorithm could progress pretty much as the line contour algorithm does,
  123. * taking each polygon as it is found, but then at the end there would have to
  124. * be an extra step to identify the parent non-hole polygon for each hole.
  125. * This is not a particularly onerous task but it does not scale well and can
  126. * easily dominate the execution time of the contour finding for even modest
  127. * problems. It is much better to identity each hole's parent non-hole during
  128. * the sweep algorithm.
  129. *
  130. * This requirement dictates the order that filled contours are identified. As
  131. * the algorithm sweeps up through the grid, every time a polygon passes
  132. * through a quad a ParentCache object is updated with the new possible parent.
  133. * When a new hole polygon is started, the ParentCache is used to find the
  134. * first possible parent in the same quad or to the S of it. Great care is
  135. * needed each time a new quad is checked to see if a new polygon should be
  136. * started, as a single quad can have multiple polygon starts, e.g. a quad
  137. * could be a saddle quad for both lower and upper contour levels, meaning it
  138. * has four contour line segments passing through it which could all be from
  139. * different polygons. The S-most polygon must be started first, then the next
  140. * S-most and so on until the N-most polygon is started in that quad.
  141. */
  142. #ifndef CONTOURPY_MPL_2014_H
  143. #define CONTOURPY_MPL_2014_H
  144. #include "contour_generator.h"
  145. #include <list>
  146. #include <iostream>
  147. #include <vector>
  148. namespace contourpy {
  149. namespace mpl2014 {
  150. // Edge of a quad including diagonal edges of masked quads if _corner_mask true.
  151. typedef enum
  152. {
  153. // Listing values here so easier to check for debug purposes.
  154. Edge_None = -1,
  155. Edge_E = 0,
  156. Edge_N = 1,
  157. Edge_W = 2,
  158. Edge_S = 3,
  159. // The following are only used if _corner_mask is true.
  160. Edge_NE = 4,
  161. Edge_NW = 5,
  162. Edge_SW = 6,
  163. Edge_SE = 7
  164. } Edge;
  165. // Combination of a quad and an edge of that quad.
  166. // An invalid quad edge has quad of -1.
  167. struct QuadEdge
  168. {
  169. QuadEdge() = delete;
  170. QuadEdge(index_t quad_, Edge edge_);
  171. bool operator==(const QuadEdge& other) const;
  172. friend std::ostream& operator<<(std::ostream& os, const QuadEdge& quad_edge);
  173. index_t quad;
  174. Edge edge;
  175. };
  176. // 2D point with x,y coordinates.
  177. struct XY
  178. {
  179. XY() = delete;
  180. XY(const double& x_, const double& y_);
  181. bool operator==(const XY& other) const;
  182. friend std::ostream& operator<<(std::ostream& os, const XY& xy);
  183. double x, y;
  184. };
  185. // A single line of a contour, which may be a closed line loop or an open line
  186. // strip. Identical adjacent points are avoided using push_back().
  187. // A ContourLine is either a hole (points ordered clockwise) or it is not
  188. // (points ordered anticlockwise). Each hole has a parent ContourLine that is
  189. // not a hole; each non-hole contains zero or more child holes. A non-hole and
  190. // its child holes must be rendered together to obtain the correct results.
  191. class ContourLine : public std::vector<XY>
  192. {
  193. public:
  194. typedef std::list<ContourLine*> Children;
  195. explicit ContourLine(bool is_hole);
  196. void add_child(ContourLine* child);
  197. void clear_parent();
  198. const Children& get_children() const;
  199. const ContourLine* get_parent() const;
  200. ContourLine* get_parent();
  201. bool is_hole() const;
  202. void set_parent(ContourLine* parent);
  203. void write() const;
  204. private:
  205. bool _is_hole;
  206. ContourLine* _parent; // Only set if is_hole, not owned.
  207. Children _children; // Only set if !is_hole, not owned.
  208. };
  209. // A Contour is a collection of zero or more ContourLines.
  210. class Contour : public std::vector<ContourLine*>
  211. {
  212. public:
  213. Contour();
  214. virtual ~Contour();
  215. void delete_contour_lines();
  216. void write() const;
  217. };
  218. // Single chunk of ContourLine parents, indexed by quad. As a chunk's filled
  219. // contours are created, the ParentCache is updated each time a ContourLine
  220. // passes through each quad. When a new ContourLine is created, if it is a
  221. // hole its parent ContourLine is read from the ParentCache by looking at the
  222. // start quad, then each quad to the S in turn until a non-zero ContourLine is
  223. // found.
  224. class ParentCache
  225. {
  226. public:
  227. ParentCache(index_t nx, index_t x_chunk_points, index_t y_chunk_points);
  228. ContourLine* get_parent(index_t quad);
  229. void set_chunk_starts(index_t istart, index_t jstart);
  230. void set_parent(index_t quad, ContourLine& contour_line);
  231. private:
  232. index_t index_to_index(index_t quad) const;
  233. index_t _nx;
  234. index_t _x_chunk_points, _y_chunk_points; // Number of points not quads.
  235. std::vector<ContourLine*> _lines; // Not owned.
  236. index_t _istart, _jstart;
  237. };
  238. // See overview of algorithm at top of file.
  239. class Mpl2014ContourGenerator : public ContourGenerator
  240. {
  241. public:
  242. // Constructor with optional mask.
  243. // x, y, z: double arrays of shape (ny,nx).
  244. // mask: boolean array, ether empty (if no mask), or of shape (ny,nx).
  245. // corner_mask: flag for different masking behaviour.
  246. // x_chunk_size: 0 for no chunking, or +ve integer for size of chunks that
  247. // the x-direction is subdivided into.
  248. // y_chunk_size: 0 for no chunking, or +ve integer for size of chunks that
  249. // the y-direction is subdivided into.
  250. Mpl2014ContourGenerator(
  251. const CoordinateArray& x, const CoordinateArray& y, const CoordinateArray& z,
  252. const MaskArray& mask, bool corner_mask, index_t x_chunk_size, index_t y_chunk_size);
  253. // Destructor.
  254. ~Mpl2014ContourGenerator();
  255. // Create and return polygons for a filled contour between the two
  256. // specified levels.
  257. py::tuple filled(const double& lower_level, const double& upper_level);
  258. py::tuple get_chunk_count() const; // Return (y_chunk_count, x_chunk_count)
  259. py::tuple get_chunk_size() const; // Return (y_chunk_size, x_chunk_size)
  260. bool get_corner_mask() const;
  261. // Create and return polygons for a line (i.e. non-filled) contour at the
  262. // specified level.
  263. py::tuple lines(const double& level);
  264. private:
  265. // Typedef for following either a boundary of the domain or the interior;
  266. // clearer than using a boolean.
  267. typedef enum
  268. {
  269. Boundary,
  270. Interior
  271. } BoundaryOrInterior;
  272. // Typedef for direction of movement from one quad to the next.
  273. typedef enum
  274. {
  275. Dir_Right = -1,
  276. Dir_Straight = 0,
  277. Dir_Left = +1
  278. } Dir;
  279. // Typedef for a polygon being a hole or not; clearer than using a boolean.
  280. typedef enum
  281. {
  282. NotHole,
  283. Hole
  284. } HoleOrNot;
  285. // Append a C++ ContourLine to the end of two python lists. Used for line
  286. // contours where each ContourLine is converted to a separate numpy array
  287. // of (x,y) points and a second numpy array of 'kinds' or 'codes'.
  288. // Clears the ContourLine too.
  289. void append_contour_line_to_vertices_and_codes(
  290. ContourLine& contour_line, py::list& vertices_list, py::list& codes_list) const;
  291. // Append a C++ Contour to the end of two python lists. Used for filled
  292. // contours where each non-hole ContourLine and its child holes are
  293. // represented by a numpy array of (x,y) points and a second numpy array of
  294. // 'kinds' or 'codes' that indicates where the points array is split into
  295. // individual polygons.
  296. // Clears the Contour too, freeing each ContourLine as soon as possible
  297. // for minimum RAM usage.
  298. void append_contour_to_vertices_and_codes(
  299. Contour& contour, py::list& vertices_list, py::list& codes_list) const;
  300. // Return number of chunks that fit in the specified point_count.
  301. static index_t calc_chunk_count(index_t point_count, index_t chunk_size);
  302. // Return actual chunk_size from specified point_count and requested chunk_size.
  303. static index_t calc_chunk_size(index_t point_count, index_t chunk_size);
  304. // Append the point on the specified QuadEdge that intersects the specified
  305. // level to the specified ContourLine.
  306. void edge_interp(const QuadEdge& quad_edge, const double& level, ContourLine& contour_line);
  307. // Follow a contour along a boundary, appending points to the ContourLine
  308. // as it progresses. Only called for filled contours. Stops when the
  309. // contour leaves the boundary to move into the interior of the domain, or
  310. // when the start_quad_edge is reached in which case the ContourLine is a
  311. // completed closed loop. Always adds the end point of each boundary edge
  312. // to the ContourLine, regardless of whether moving to another boundary
  313. // edge or leaving the boundary into the interior. Never adds the start
  314. // point of the first boundary edge to the ContourLine.
  315. // contour_line: ContourLine to append points to.
  316. // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge
  317. // that is stopped on.
  318. // lower_level: lower contour z-value.
  319. // upper_level: upper contour z-value.
  320. // level_index: level index started on (1 = lower, 2 = upper level).
  321. // start_quad_edge: QuadEdge that the ContourLine started from, which is
  322. // used to check if the ContourLine is finished.
  323. // Returns the end level_index.
  324. unsigned int follow_boundary(
  325. ContourLine& contour_line, QuadEdge& quad_edge, const double& lower_level,
  326. const double& upper_level, unsigned int level_index, const QuadEdge& start_quad_edge);
  327. // Follow a contour across the interior of the domain, appending points to
  328. // the ContourLine as it progresses. Called for both line and filled
  329. // contours. Stops when the contour reaches a boundary or, if the
  330. // start_quad_edge is specified, when quad_edge == start_quad_edge and
  331. // level_index == start_level_index. Always adds the end point of each
  332. // quad traversed to the ContourLine; only adds the start point of the
  333. // first quad if want_initial_point flag is true.
  334. // contour_line: ContourLine to append points to.
  335. // quad_edge: on entry the QuadEdge to start from, on exit the QuadEdge
  336. // that is stopped on.
  337. // level_index: level index started on (1 = lower, 2 = upper level).
  338. // level: contour z-value.
  339. // want_initial_point: whether want to append the initial point to the
  340. // ContourLine or not.
  341. // start_quad_edge: the QuadEdge that the ContourLine started from to
  342. // check if the ContourLine is finished, or 0 if no check should occur.
  343. // start_level_index: the level_index that the ContourLine started from.
  344. // set_parents: whether should set ParentCache as it progresses or not.
  345. // This is true for filled contours, false for line contours.
  346. void follow_interior(
  347. ContourLine& contour_line, QuadEdge& quad_edge, unsigned int level_index,
  348. const double& level, bool want_initial_point, const QuadEdge* start_quad_edge,
  349. unsigned int start_level_index, bool set_parents);
  350. // Return the index limits of a particular chunk.
  351. void get_chunk_limits(
  352. index_t ijchunk, index_t& ichunk, index_t& jchunk, index_t& istart, index_t& iend,
  353. index_t& jstart, index_t& jend);
  354. // Check if a contour starts within the specified corner quad on the
  355. // specified level_index, and if so return the start edge. Otherwise
  356. // return Edge_None.
  357. Edge get_corner_start_edge(index_t quad, unsigned int level_index) const;
  358. // Return index of point at start or end of specified QuadEdge, assuming
  359. // anticlockwise ordering around non-masked quads.
  360. index_t get_edge_point_index(const QuadEdge& quad_edge, bool start) const;
  361. // Return the edge to exit a quad from, given the specified entry quad_edge
  362. // and direction to move in.
  363. Edge get_exit_edge(const QuadEdge& quad_edge, Dir dir) const;
  364. const double& get_point_x(index_t point) const;
  365. const double& get_point_y(index_t point) const;
  366. // Append the (x,y) coordinates of the specified point index to the
  367. // specified ContourLine.
  368. void get_point_xy(index_t point, ContourLine& contour_line) const;
  369. // Return the z-value of the specified point index.
  370. const double& get_point_z(index_t point) const;
  371. // Check if a contour starts within the specified non-corner quad on the
  372. // specified level_index, and if so return the start edge. Otherwise
  373. // return Edge_None.
  374. Edge get_quad_start_edge(index_t quad, unsigned int level_index) const;
  375. // Check if a contour starts within the specified quad, whether it is a
  376. // corner or a full quad, and if so return the start edge. Otherwise
  377. // return Edge_None.
  378. Edge get_start_edge(index_t quad, unsigned int level_index) const;
  379. // Initialise the cache to contain grid information that is constant
  380. // across the lifetime of this object, i.e. does not vary between calls to
  381. // create_contour() and create_filled_contour().
  382. void init_cache_grid(const MaskArray& mask);
  383. // Initialise the cache with information that is specific to contouring the
  384. // specified two levels. The levels are the same for contour lines,
  385. // different for filled contours.
  386. void init_cache_levels(const double& lower_level, const double& upper_level);
  387. // Append the (x,y) point at which the level intersects the line connecting
  388. // the two specified point indices to the specified ContourLine.
  389. void interp(
  390. index_t point1, index_t point2, const double& level, ContourLine& contour_line) const;
  391. // Return true if the specified QuadEdge is a boundary, i.e. is either an
  392. // edge between a masked and non-masked quad/corner or is a chunk boundary.
  393. bool is_edge_a_boundary(const QuadEdge& quad_edge) const;
  394. // Follow a boundary from one QuadEdge to the next in an anticlockwise
  395. // manner around the non-masked region.
  396. void move_to_next_boundary_edge(QuadEdge& quad_edge) const;
  397. // Move from the quad specified by quad_edge.quad to the neighbouring quad
  398. // by crossing the edge specified by quad_edge.edge.
  399. void move_to_next_quad(QuadEdge& quad_edge) const;
  400. // Check for filled contours starting within the specified quad and
  401. // complete any that are found, appending them to the specified Contour.
  402. void single_quad_filled(
  403. Contour& contour, index_t quad, const double& lower_level, const double& upper_level);
  404. // Start and complete a filled contour line.
  405. // quad: index of quad to start ContourLine in.
  406. // edge: edge of quad to start ContourLine from.
  407. // start_level_index: the level_index that the ContourLine starts from.
  408. // hole_or_not: whether the ContourLine is a hole or not.
  409. // boundary_or_interior: whether the ContourLine starts on a boundary or
  410. // the interior.
  411. // lower_level: lower contour z-value.
  412. // upper_level: upper contour z-value.
  413. // Returns newly created ContourLine.
  414. ContourLine* start_filled(
  415. index_t quad, Edge edge, unsigned int start_level_index, HoleOrNot hole_or_not,
  416. BoundaryOrInterior boundary_or_interior, const double& lower_level,
  417. const double& upper_level);
  418. // Start and complete a line contour that both starts and end on a
  419. // boundary, traversing the interior of the domain.
  420. // vertices_list: Python list that the ContourLine should be appended to.
  421. // codes_list: Python list that the kind codes should be appended to.
  422. // quad: index of quad to start ContourLine in.
  423. // edge: boundary edge to start ContourLine from.
  424. // level: contour z-value.
  425. // Returns true if the start quad does not need to be visited again, i.e.
  426. // VISITED(quad,1).
  427. bool start_line(
  428. py::list& vertices_list, py::list& codes_list, index_t quad, Edge edge,
  429. const double& level);
  430. // Debug function that writes the cache status to stdout.
  431. //void write_cache(bool grid_only = false) const;
  432. // Debug function that writes that cache status for a single quad to
  433. // stdout.
  434. //void write_cache_quad(index_t quad, bool grid_only) const;
  435. // Note that mask is not stored as once it has been used to initialise the
  436. // cache it is no longer needed.
  437. CoordinateArray _x, _y, _z;
  438. index_t _nx, _ny; // Number of points in each direction.
  439. index_t _n; // Total number of points (and hence quads).
  440. bool _corner_mask;
  441. index_t _x_chunk_size; // Number of quads per chunk (not points).
  442. index_t _y_chunk_size; // Always > 0.
  443. index_t _nxchunk, _nychunk; // Number of chunks in each direction.
  444. index_t _chunk_count; // Total number of chunks.
  445. typedef uint32_t CacheItem;
  446. CacheItem* _cache;
  447. ParentCache _parent_cache; // On W quad sides.
  448. };
  449. } // namespace mpl2014
  450. } // namespace contourpy
  451. #endif // #define CONTOURPY_MPL_2014_H