File size: 13,470 Bytes
a5ffdcd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
/*
**********************************************************************************
**
** This file was created for the LibreCAD project (librecad.org), a 2D CAD program.
**
** Copyright (C) 2023 librecad (www.librecad.org)
** Copyright (C) 2023 dxli (github.com/dxli)
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version 2
** of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**
**********************************************************************************
*/
// File: lc_looputils.h
#ifndef LC_LOOPUTILS_H
#define LC_LOOPUTILS_H

#include <map>
#include <memory>
#include <vector>

class QPainterPath;
class RS_AtomicEntity;
class RS_Entity;
class RS_EntityContainer;
class RS_Painter;
class RS_Pattern;
class RS_Vector;
class RS_VectorSolutions;

namespace lc {
namespace geo {
class Area;
}
}
using LC_Rect = lc::geo::Area;

/**
 * @brief The namespace contains utilities to analyze contour topology.
 * The initial motivation is to allow proper calculation of hatched areas. For example, the hatched areas
 * contain holes (say, denoted as lakes), and the lakes may further contain islands of their own. The hatched
 * area calculation should find the total area of all land including any islands in lakes.
 */
namespace LC_LoopUtils {

/**
 * @brief The LC_Loops class - recursive representation of contour loops with holes.
 * Represents a hierarchical structure for contours, where each loop can have child loops (holes or islands).
 * Supports safe ownership of entities via smart pointers and cloning.
 */
class LC_Loops {
public:
  /**
   * @brief Constructor for LC_Loops.
   * @param loop Shared pointer to the entity container representing the outer loop.
   * @param ownsEntities Flag indicating if this object owns the entities (default: true).
   */
  LC_Loops(std::shared_ptr<RS_EntityContainer> loop = std::make_shared<RS_EntityContainer>(), bool ownsEntities = true);
  ~LC_Loops();

  /**
   * @brief Adds a child loop to this loop's hierarchy.
   * @param child The child LC_Loops to add.
   */
  void addChild(LC_Loops child);
  /**
   * @brief Adds an entity to the outer loop.
   * @param entity Pointer to the entity to add.
   */
  void addEntity(RS_Entity* entity);
  /**
   * @brief Checks if a point is inside this loop (considering odd-even rule for hierarchy).
   * @param point The point to test.
   * @return True if inside.
   */
  bool isInside(const RS_Vector& point) const;
  /**
   * @brief Computes the containing depth of a point in the hierarchy.
   * @param point The point to test.
   * @return The winding depth (odd means inside).
   */
  int getContainingDepth(const RS_Vector& point) const;
  /**
   * @brief Generates a QPainterPath for this loop and its children up to the specified level.
   * @param level Recursion level for path building (default: 0).
   * @return The combined QPainterPath with OddEvenFill rule.
   */
  QPainterPath getPainterPath(RS_Painter* painter, int level = 0) const;
  /**
   * @brief Trims pattern entities to the boundaries of this loop hierarchy.
   * @param pattern The pattern to trim.
   * @return Unique pointer to a container of trimmed entities.
   */
  std::unique_ptr<RS_EntityContainer> trimPatternEntities(const RS_Pattern& pattern) const;
  /**
   * @brief Computes the total area of this loop, adding islands and subtracting holes.
   * @return The net area as a double.
   */
  double getTotalArea() const;
  /**
   * @brief Checks if this loop overlaps with a given rectangle.
   * @param other The rectangle to check against.
   * @return True if overlap exists.
   */
  bool overlap(const LC_Rect& other) const;
  /**
   * @brief Gets the outer loop container.
   * @return Shared pointer to the outer RS_EntityContainer.
   */
  std::shared_ptr<RS_EntityContainer> getOuterLoop() const {
    return m_loop;
  }

  /**
   * @brief Gets the child loops.
   * @return Const reference to the vector of children.
   */
  const std::vector<LC_Loops>& getChildren() const {
    return m_children;
  }

private:
  /**
   * @brief Checks if a point is inside the outer loop only (non-recursive).
   * @param point The point to test.
   * @return True if inside the outer contour.
   */
  bool isInsideOuter(const RS_Vector& point) const;
  /**
   * @brief Builds a QPainterPath from the entities in a container.
   * @param cont The entity container.
   * @return The resulting path.
   */
  QPainterPath buildPathFromLoop(RS_Painter* painter, const RS_EntityContainer& cont) const;
  /**
   * @brief Collects all descendant loops recursively.
   * @param loops Output vector of loop pointers.
   */
  void getAllLoops(std::vector<const RS_EntityContainer*>& loops) const;
  /**
   * @brief Computes the bounding box of the outer loop.
   * @return The LC_Rect bounding box.
   */
  LC_Rect getBoundingBox() const;
  /**
   * @brief Alias for isInside (odd-even rule).
   * @param p The point.
   * @return True if inside.
   */
  bool isPointInside(const RS_Vector& p) const;
  /**
   * @brief Parametric point on ellipse.
   * @param center Ellipse center.
   * @param major Major radius.
   * @param minor Minor radius.
   * @param rot Rotation angle.
   * @param t Parameter.
   * @return The point vector.
   */
  RS_Vector e_point(const RS_Vector& center, double major, double minor, double rot, double t) const;
  /**
   * @brief Derivative (tangent) for ellipse parametric.
   * @param major Major radius.
   * @param minor Minor radius.
   * @param rot Rotation.
   * @param t Parameter.
   * @return Tangent vector.
   */
  RS_Vector e_prime(double major, double minor, double rot, double t) const;
  /**
   * @brief Adds an elliptic arc to a QPainterPath using cubic Beziers.
   * @param path The path to append to.
   * @param center Center.
   * @param major Major radius.
   * @param minor Minor radius.
   * @param rot Rotation.
   * @param a1 Start angle.
   * @param a2 End angle.
   */
  void addEllipticArc(QPainterPath& path, const RS_Vector& center, double major, double minor, double rot, double a1, double a2) const;
  /**
   * @brief Collects all atomic boundary entities from this hierarchy.
   * @return Vector of RS_Entity pointers.
   */
  std::vector<RS_Entity*> getAllBoundaries() const;
  /**
   * @brief Sorts intersection points along an entity by parameter.
   * @param e The entity.
   * @param inters Unsorted intersections.
   * @return Sorted vector of points.
   */
  std::vector<RS_Vector> sortPointsAlongEntity(RS_Entity* e, std::vector<RS_Vector> inters) const;
  /**
   * @brief Creates a sub-entity between two points.
   * @param e Original entity.
   * @param p1 Start point.
   * @param p2 End point.
   * @return New entity or nullptr.
   */
  RS_Entity* createSubEntity(RS_Entity* e, const RS_Vector& p1, const RS_Vector& p2) const;
  /**
   * @brief Checks if an entity is closed (full loop).
   * @param e The entity.
   * @return True if closed.
   */
  bool isEntityClosed(const RS_Entity* e) const;
  /**
   * @brief Creates tile offsets for a pattern within the bounding box.
   * @param pattern The pattern.
   * @return Vector of tile offsets.
   */
  std::vector<RS_Vector> createTiles(const RS_Pattern& pattern) const;

  std::shared_ptr<RS_EntityContainer> m_loop;  ///< Outer loop container
  std::vector<LC_Loops> m_children;             ///< Child loops (holes/islands)
};

/**
 * @brief The LoopExtractor class, to extract closed loops from edges.
 * Processes a container of edges to form closed loops, handling connectivity and orientation.
 *
 *  * The algorithm:
 * 0. Mark all edges as unprocessed.
 * 1. Draw a line crossing the first edge and find intersections with all unprocessed edges; select the edge with the closest intersection to the line start as the first edge on an outermost loop.
 * 2. Set the start or end point as the target point based on direction for counterclockwise traversal.
 * 3. Mark the edge as processed and add to the current loop.
 * 4. From the current end point, find all unprocessed edges connected to it.
 * 5. If one connected edge, use it as next; if multiple, draw a small circle around the end point, find intersections with the current and connected edges.
 * 6. Calculate angles from the end point to these intersections.
 * 7. Sort by the smallest left-turning angle difference from the current edge's angle to find the next outermost edge.
 * 8. Update the current edge and end point.
 * 9. Repeat steps 4-8 until the end point matches the target point, closing the loop.
 * 10. Add the closed loop and repeat steps 1-9 until all edges are processed.
 *
 * The algorithm has the following assumptions:
 * 1. Contours are closed loops, so each edge has its start/end points connected to other edges;
 * 2. Each loop is simply closed with number of edges equals the number of vertices (Shared endpoints),
 * i.e. Euler characteristic 0;
 * 3. Full circles/ellipses are accepted as individual closed contours;
 * 4. No self-intersection among contours; i.e. no edge crosses another edge;
 * 5. Multiple edges are allowed to be connected at a single point;
 * 6. Closed contours may share edge endpoints, but no edge is shared by more than one contours.
 */
class LoopExtractor {

public:
  /**
   * @brief Constructor for LoopExtractor.
   * @param edges Container of edges to process.
   */
  LoopExtractor(const RS_EntityContainer& edges);
  ~LoopExtractor();

  /**
   * @brief Extracts all closed loops from the edges.
   * @return Vector of unique_ptr to loop containers.
   */
  std::vector<std::unique_ptr<RS_EntityContainer>> extract();

private:
  // validate the loop m_loop
  /**
   * @brief Validates the current loop for closure.
   * @return True if valid.
   */
  bool validate() const;

  /**
   * @brief Finds the first edge to start a loop.
   * @return Starting entity or nullptr.
   */
  RS_Entity* findFirst() const;

  /**
   * @brief Finds the next connected edge in the loop.
   * @return True if found.
   */
  bool findNext() const;

  /**
   * @brief Gets entities connected to the current endpoint.
   * @return Vector of connected entities.
   */
  std::vector<RS_Entity*> getConnected() const;

  /**
   * @brief Selects the outermost (preferred direction) from connected edges.
   * @param edges Connected edges.
   * @return Selected entity.
   */
  RS_Entity* findOutermost(std::vector<RS_Entity*> edges) const;

         // Tolerance for endpoint matching
  static constexpr double ENDPOINT_TOLERANCE = 1e-10;

  mutable std::unique_ptr<RS_EntityContainer> m_loop;  ///< Current loop being built

  struct LoopData;
  std::unique_ptr<LoopData> m_data;  ///< Private data for extraction state
};

/**
 * @brief The LoopSorter class - find topologic relations of loops
 * Sorts loops by containment to build a hierarchy, filtering degenerates and assigning parents.
 *
 * Each input loop is assumed to be a simple closed loop, and contains only edges.
 * The input loops should not contain sub-loops

 */
class LoopSorter {
public:
  LoopSorter(std::vector<std::unique_ptr<RS_EntityContainer>> loops);
  ~LoopSorter();

  struct AreaPredicate;  // Sorts by ascending absolute area (small to large), ties by bounding box diagonal

  /**
   * @brief Gets the sorted hierarchical results.
   * @return Shared pointer to vector of LC_Loops.
   */
  std::shared_ptr<std::vector<LC_Loops>> getResults() const;

private:
  /**
   * @brief Sorts loops and builds the hierarchy forest.
   */
  void sortAndBuild();
  /**
   * @brief Initializes data structures.
   */
  void init();

  /**
   * @brief Finds and assigns the parent for a loop.
   * @param loop The child loop.
   * @param sorted Sorted list of all loops.
   */
  void findParent(RS_EntityContainer* loop, const std::multimap<double, RS_EntityContainer*>& sorted);
  /**
   * @brief Converts a forest of containers to LC_Loops trees.
   * @param forest Vector of root containers.
   * @return Shared vector of LC_Loops.
   */
  std::shared_ptr<std::vector<LC_Loops>> forestToLoops(std::vector<RS_EntityContainer*> forest) const;

  struct Data;
  std::unique_ptr<Data> m_data;  ///< Private data for sorting state
};

/**
 * @brief The LoopOptimizer class - separate a collection of contours into loops
 * High-level class to extract, sort, and build hierarchical loops from contours.
 */
class LoopOptimizer {
public:
  LoopOptimizer(const RS_EntityContainer& contour);
  ~LoopOptimizer();

  /**
   * @brief Gets the optimized hierarchical results.
   * @return Shared pointer to vector of LC_Loops.
   */
  std::shared_ptr<std::vector<LC_Loops>> GetResults() const;

private:
  /**
   * @brief Adds and processes a contour container.
   * @param contour The input contour.
   */
  void AddContainer(const RS_EntityContainer& contour);

  struct Data;
  std::shared_ptr<Data> m_data;  ///< Private data for optimization results
};
}

#endif // LC_LOOPUTILS_H