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
|