Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
intersection-graph.cpp
Go to the documentation of this file.
1/*
5 * Authors:
6 * Krzysztof Kosiński <tweenk.pl@gmail.com>
7 *
8 * Copyright 2015 Authors
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it either under the terms of the GNU Lesser General Public
12 * License version 2.1 as published by the Free Software Foundation
13 * (the "LGPL") or, at your option, under the terms of the Mozilla
14 * Public License Version 1.1 (the "MPL"). If you do not alter this
15 * notice, a recipient may use your version of this file under either
16 * the MPL or the LGPL.
17 *
18 * You should have received a copy of the LGPL along with this library
19 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * You should have received a copy of the MPL along with this library
22 * in the file COPYING-MPL-1.1
23 *
24 * The contents of this file are subject to the Mozilla Public License
25 * Version 1.1 (the "License"); you may not use this file except in
26 * compliance with the License. You may obtain a copy of the License at
27 * http://www.mozilla.org/MPL/
28 *
29 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
30 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
31 * the specific language governing rights and limitations.
32 */
33
35#include <2geom/path.h>
36#include <2geom/pathvector.h>
37#include <2geom/utils.h>
38#include <iostream>
39#include <iterator>
40
41namespace Geom {
42
44struct PathIntersectionGraph::IntersectionVertexLess {
45 bool operator()(IntersectionVertex const &a, IntersectionVertex const &b) const {
46 return a.pos < b.pos;
47 }
48};
49
51 : _graph_valid(true)
52{
53 _pv[0] = a;
54 _pv[1] = b;
55
56 if (a.empty() || b.empty()) return;
57
59 bool has_intersections = _prepareIntersectionLists(precision);
60 if (!has_intersections) return;
61
63
64 // If a path has only degenerate intersections, assign its status now.
65 // This protects against later accidentally picking a point for winding
66 // determination that is exactly at a removed intersection.
69 if (_graph_valid) {
70 _verify();
71 }
72}
73
78{
79 // all paths must be closed, otherwise we will miss some intersections
80 for (auto & w : _pv) {
81 for (auto & i : w) {
82 i.close();
83 }
84 }
85 // remove degenerate segments
86 for (auto & w : _pv) {
87 for (std::size_t i = w.size(); i > 0; --i) {
88 if (w[i-1].empty()) {
89 w.erase(w.begin() + (i-1));
90 continue;
91 }
92 for (std::size_t j = w[i-1].size(); j > 0; --j) {
93 if (w[i-1][j-1].isDegenerate()) {
94 w[i-1].erase(w[i-1].begin() + (j-1));
95 }
96 }
97 }
98 }
99}
100
106{
107 std::vector<PVIntersection> pxs = _pv[0].intersect(_pv[1], precision);
108 // NOTE: this early return means that the path data structures will not be created
109 // if there are no intersections at all!
110 if (pxs.empty()) return false;
111
112 // prepare intersection lists for each path component
113 for (unsigned w = 0; w < 2; ++w) {
114 for (std::size_t i = 0; i < _pv[w].size(); ++i) {
115 _components[w].push_back(new PathData(w, i));
116 }
117 }
118
119 // create intersection vertices
120 for (auto & px : pxs) {
121 IntersectionVertex *xa, *xb;
122 xa = new IntersectionVertex();
123 xb = new IntersectionVertex();
124 //xa->processed = xb->processed = false;
125 xa->which = 0; xb->which = 1;
126 xa->pos = px.first;
127 xb->pos = px.second;
128 xa->p = xb->p = px.point();
129 xa->neighbor = xb;
130 xb->neighbor = xa;
131 xa->next_edge = xb->next_edge = OUTSIDE;
132 xa->defective = xb->defective = false;
133 _xs.push_back(xa);
134 _xs.push_back(xb);
135 _components[0][xa->pos.path_index].xlist.push_back(*xa);
136 _components[1][xb->pos.path_index].xlist.push_back(*xb);
137 }
138
139 // sort intersections in each component according to time value
140 for (auto & _component : _components) {
141 for (std::size_t i = 0; i < _component.size(); ++i) {
142 _component[i].xlist.sort(IntersectionVertexLess());
143 }
144 }
145
146 return true;
147}
148
153{
154 for (unsigned w = 0; w < 2; ++w) {
155 unsigned ow = (w+1) % 2;
156
157 for (unsigned li = 0; li < _components[w].size(); ++li) { // Traverse all paths in the component
158 IntersectionList &xl = _components[w][li].xlist;
159 for (ILIter i = xl.begin(); i != xl.end(); ++i) { // Traverse all intersections in the path
160 ILIter n = cyclic_next(i, xl);
161 std::size_t pi = i->pos.path_index;
162
164 PathInterval ival = forward_interval(i->pos, n->pos, _pv[w][pi].size());
165 PathTime mid = ival.inside(precision);
166
167 Point wpoint = _pv[w][pi].pointAt(mid);
168 _winding_points.push_back(wpoint);
169 int wdg = _pv[ow].winding(wpoint);
170 if (wdg % 2) {
171 i->next_edge = INSIDE;
172 } else {
173 i->next_edge = OUTSIDE;
174 }
175 }
176 }
177 }
178}
179
184{
185 for (auto & _component : _components) {
186 for (unsigned li = 0; li < _component.size(); ++li) {
187 IntersectionList &xl = _component[li].xlist;
188 bool has_in = false;
189 bool has_out = false;
190 for (auto & i : xl) {
191 has_in |= (i.next_edge == INSIDE);
192 has_out |= (i.next_edge == OUTSIDE);
193 }
194 if (has_in && !has_out) {
195 _component[li].status = INSIDE;
196 }
197 if (!has_in && has_out) {
198 _component[li].status = OUTSIDE;
199 }
200 }
201 }
202}
203
210{
211 for (auto & _component : _components) {
212 for (unsigned li = 0; li < _component.size(); ++li) {
213 IntersectionList &xl = _component[li].xlist;
214 for (ILIter i = xl.begin(); i != xl.end();) {
215 ILIter n = cyclic_next(i, xl);
216 if (i->next_edge == n->next_edge) { // Both edges inside or both outside
217 bool last_node = (i == n);
218 ILIter nn = _getNeighbor(n);
220
221 // When exactly 3 out of 4 edges adjacent to an intersection
222 // have the same winding, we have a defective intersection,
223 // which is neither degenerate nor normal. Those can occur in paths
224 // that contain overlapping segments.
225 if (cyclic_prior(nn, oxl)->next_edge != nn->next_edge) {
226 // Not a backtrack - set the defective flag.
227 _graph_valid = false;
228 n->defective = true;
229 nn->defective = true;
230 ++i;
231 continue;
232 }
233 // Erase the degenerate or defective crossings
234 oxl.erase(nn);
235 xl.erase(n);
236 if (last_node) break;
237 } else {
238 ++i;
239 }
240 }
241 }
242 }
243}
244
249{
250#ifndef NDEBUG
251 for (auto & _component : _components) {
252 for (unsigned li = 0; li < _component.size(); ++li) {
253 IntersectionList &xl = _component[li].xlist;
254 assert(xl.size() % 2 == 0);
255 for (ILIter i = xl.begin(); i != xl.end(); ++i) {
256 ILIter j = cyclic_next(i, xl);
257 assert(i->next_edge != j->next_edge);
258 }
259 }
260 }
261#endif
262}
263
271
279
287
295
297{
298 PathVector r1, r2;
299 r1 = getAminusB();
300 r2 = getBminusA();
301 std::copy(r2.begin(), r2.end(), std::back_inserter(r1));
302 return r1;
303}
304
306{
307 std::size_t result = 0;
308 for (std::size_t i = 0; i < _components[0].size(); ++i) {
309 result += _components[0][i].xlist.size();
310 }
311 return result;
312}
313
314std::vector<Point> PathIntersectionGraph::intersectionPoints(bool defective) const
315{
316 std::vector<Point> result;
317
318 for (std::size_t i = 0; i < _components[0].size(); ++i) {
319 for (const auto & j : _components[0][i].xlist) {
320 if (j.defective == defective) {
321 result.push_back(j.p);
322 }
323 }
324 }
325 return result;
326}
327
329{
330 typedef boost::ptr_vector<PathData>::const_iterator PIter;
331 for (unsigned w = 0; w < 2; ++w) {
332 for (PIter li = _components[w].begin(); li != _components[w].end(); ++li) {
333 for (CILIter k = li->xlist.begin(); k != li->xlist.end(); ++k) {
334 CILIter n = cyclic_next(k, li->xlist);
335 // TODO: investigate why non-contiguous paths are sometimes generated here
336 Path frag(k->p);
337 frag.setStitching(true);
338 PathInterval ival = forward_interval(k->pos, n->pos, _pv[w][k->pos.path_index].size());
339 _pv[w][k->pos.path_index].appendPortionTo(frag, ival, k->p, n->p);
340 if (k->next_edge == INSIDE) {
341 in.push_back(frag);
342 } else {
343 out.push_back(frag);
344 }
345 }
346 }
347 }
348}
349
364{
366 if (_xs.empty()) return result;
367
368 // Create the list of intersections to process
369 _ulist.clear();
370 for (auto & _component : _components) {
371 for (auto & li : _component) {
372 for (auto & k : li.xlist) {
373 _ulist.push_back(k);
374 }
375 }
376 }
377
378 unsigned n_processed = 0;
379
380 while (true) {
381 // get unprocessed intersection
382 if (_ulist.empty()) break;
383 IntersectionVertex &iv = _ulist.front();
384 unsigned w = iv.which;
385 ILIter i = _components[w][iv.pos.path_index].xlist.iterator_to(iv);
386
387 result.push_back(Path(i->p));
388 result.back().setStitching(true);
389 bool reverse = false;
390 while (i->_proc_hook.is_linked()) {
391 ILIter prev = i;
392 std::size_t pi = i->pos.path_index;
393 // determine which direction to go
394 // union: always go outside
395 // intersection: always go inside
396 // a minus b: go inside in b, outside in a
397 // b minus a: go inside in a, outside in b
398 if (w == 0) { // The path we're on is a part of A
399 reverse = (i->next_edge == INSIDE) ^ enter_a;
400 } else { // The path we're on is a part of B
401 reverse = (i->next_edge == INSIDE) ^ enter_b;
402 }
403
404 // get next intersection
405 if (reverse) {
406 i = cyclic_prior(i, _components[w][pi].xlist);
407 } else {
408 i = cyclic_next(i, _components[w][pi].xlist);
409 }
410
411 // append portion of path to the result
413 prev->pos.asPathTime(), i->pos.asPathTime(),
414 reverse, _pv[i->which][pi].size());
415
416 _pv[i->which][pi].appendPortionTo(result.back(), ival, prev->p, i->p);
417
418 // count both vertices as processed
419 n_processed += 2;
420 if (prev->_proc_hook.is_linked()) {
421 _ulist.erase(_ulist.iterator_to(*prev));
422 }
423 if (i->_proc_hook.is_linked()) {
424 _ulist.erase(_ulist.iterator_to(*i));
425 }
426
427 // switch to the other path
428 i = _getNeighbor(i);
429 w = i->which;
430 }
431 result.back().close(true);
432 if (reverse){
433 result.back() = result.back().reversed();
434 }
435 if (result.back().empty()) {
436 // std::cerr << "Path is empty" << std::endl;
438 }
439 }
440
441 if (n_processed != size() * 2) {
442 // std::cerr << "Processed " << n_processed << " intersections, expected " << (size() * 2) << std::endl;
444 }
445
446 return result;
447}
448
462{
463 unsigned w = which;
464 unsigned ow = (w+1) % 2;
465
466 for (std::size_t i = 0; i < _pv[w].size(); ++i) {
467 // the path data vector might have been left empty if there were no intersections at all
468 bool has_path_data = !_components[w].empty();
469 // Skip if the path has intersections
470 if (has_path_data && !_components[w][i].xlist.empty()) continue;
471 bool path_inside = false;
472
473 // Use the status flag set in the constructor if available.
474 if (has_path_data && _components[w][i].status == INSIDE) {
475 path_inside = true;
476 } else if (has_path_data && _components[w][i].status == OUTSIDE) {
477 path_inside = false;
478 } else {
479 // The status flag is ambiguous: we evaluate the winding number of the initial point.
480 int wdg = _pv[ow].winding(_pv[w][i].initialPoint());
481 path_inside = wdg % 2 != 0;
482 }
483
484 if (path_inside == inside) {
485 result.push_back(_pv[w][i]);
486 }
487 }
488}
489
496{
497 unsigned ow = (iter->which + 1) % 2;
498 return _components[ow][iter->neighbor->pos.path_index].xlist.iterator_to(*iter->neighbor);
499}
500
504{
505 return _components[iter->which][iter->pos.path_index];
506}
507
509std::ostream &operator<<(std::ostream &os, PathIntersectionGraph const &pig)
510{
511 os << "Intersection graph:\n"
512 << pig._xs.size()/2 << " total intersections\n"
513 << pig.size() << " considered intersections\n";
514 for (std::size_t i = 0; i < pig._components[0].size(); ++i) {
515 PathIntersectionGraph::IntersectionList const &xl = pig._components[0][i].xlist;
516 for (const auto & j : xl) {
517 os << j.pos << " - " << j.neighbor->pos << " @ " << j.p << "\n";
518 }
519 }
520 return os;
521}
522
523} // namespace Geom
524
525/*
526 Local Variables:
527 mode:c++
528 c-file-style:"stroustrup"
529 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
530 indent-tabs-mode:nil
531 fill-column:99
532 End:
533*/
534// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
535
Path - a sequence of contiguous curves.
Various utility functions.
Intermediate data for computing Boolean operations on paths.
boost::intrusive::list< IntersectionVertex, boost::intrusive::member_hook< IntersectionVertex, boost::intrusive::list_member_hook<>, &IntersectionVertex::_hook > > IntersectionList
void _assignEdgeWindingParities(Coord precision)
Determine whether path portions between consecutive intersections lie inside or outside of the other ...
void _removeDegenerateIntersections()
Remove intersections that don't change between in/out.
IntersectionList::const_iterator CILIter
boost::ptr_vector< IntersectionVertex > _xs
Stores all crossings between the two shapes.
PathVector getAminusB()
Get the difference of the shapes, A ∖ B.
PathVector getXOR()
Get the symmetric difference of the shapes, A ∆ B.
void _handleNonintersectingPaths(PathVector &result, unsigned which, bool inside)
Select intersection-free path components ahead of a boolean operation based on whether they should be...
PathVector getIntersection()
Get the intersection of the shapes, A ∩ B.
boost::ptr_vector< PathData > _components[2]
Stores the crossing information for the operands.
void _assignComponentStatusFromDegenerateIntersections()
Detect the situation where a path is either entirely inside or entirely outside of the other path-vec...
void fragments(PathVector &in, PathVector &out) const
void _prepareArguments()
Prepare the operands stored in PathIntersectionGraph::_pv by closing all of their constituent paths a...
UnprocessedList _ulist
Temporarily holds all unprocessed during a boolean operation.
std::vector< Point > _winding_points
Stores sample points located on paths of the operand path-vectors, between consecutive intersections.
bool _graph_valid
Whether all intersections are regular.
std::size_t size() const
Returns the number of intersections used when computing Boolean operations.
PathIntersectionGraph(PathVector const &a, PathVector const &b, Coord precision=EPSILON)
Construct a path intersection graph for two shapes described via their boundaries.
std::vector< Point > intersectionPoints(bool defective=false) const
Get the geometric points where the two path-vectors intersect.
ILIter _getNeighbor(ILIter iter)
Get an iterator to the corresponding crossing on the other path-vector.
void _verify()
Verify that all paths contain an even number of intersections and that the intersection graph does no...
PathVector _pv[2]
Stores the two operand path-vectors, A at _pv[0] and B at _pv[1].
PathData & _getPathData(ILIter iter)
Get the path data for the path containing the intersection given an iterator to the intersection.
PathVector getUnion()
Get the union of the shapes, A ∪ B.
IntersectionList::iterator ILIter
bool _prepareIntersectionLists(Coord precision)
Compute the lists of intersections between the constituent paths of both operands.
PathVector _getResult(bool enter_a, bool enter_b)
Compute the partial result of a boolean operation by looking at components containing intersections a...
PathVector getBminusA()
Get the opposite difference of the shapes, B ∖ A.
Contiguous subset of the path's parameter domain.
Definition path.h:187
static PathInterval from_direction(PathTime const &from, PathTime const &to, bool reversed, size_type path_size)
Select one of two intervals with given endpoints by parameter direction.
Definition path.cpp:223
PathTime inside(Coord min_dist=EPSILON) const
Get a time at least min_dist away in parameter space from the ends.
Definition path.cpp:136
Sequence of subpaths.
Definition pathvector.h:122
size_type size() const
Get the number of paths in the vector.
Definition pathvector.h:147
void push_back(Path const &path)
Append a path at the end.
Definition pathvector.h:172
int winding(Point const &p) const
Determine the winding number at the specified point.
Point pointAt(Coord t) const
iterator erase(iterator i)
Remove a path from the vector.
Definition pathvector.h:187
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
iterator begin()
Definition pathvector.h:151
iterator end()
Definition pathvector.h:152
std::vector< PVIntersection > intersect(PathVector const &other, Coord precision=EPSILON) const
Sequence of contiguous curves, aka spline.
Definition path.h:353
void setStitching(bool x)
Enable or disable the throwing of exceptions when stitching discontinuities.
Definition path.h:827
Two-dimensional point that doubles as a vector.
Definition point.h:66
const double w
Definition conic-4.cpp:19
Css & result
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
Path intersection graph.
Various utility functions.
Definition affine.h:22
Bezier reverse(const Bezier &a)
Definition bezier.h:342
std::ostream & operator<<(std::ostream &os, const Bezier &b)
Definition bezier.h:372
@ GEOM_ERR_INTERSECGRAPH
Definition utils.h:45
Iter cyclic_next(Iter i, Container &c)
Get the next iterator in the container with wrap-around.
Definition utils.h:81
Iter cyclic_prior(Iter i, Container &c)
Get the previous iterator in the container with wrap-around.
Definition utils.h:93
PathVector - a sequence of subpaths.
InOutFlag next_edge
Tells us whether the edge originating at this intersection lies inside or outside of the shape given ...
IntersectionVertex * neighbor
A pointer to the corresponding vertex on the other shape.
unsigned which
Index of the operand path-vector that this intersection vertex lies on.
bool defective
Whether the intersection is defective, which means that for some reason the paths neither cross trans...
Point p
Geometric position of the intersection point; guarantees that endpoints are exact.
Stores processed intersection information for a single path in an operand path-vector.
IntersectionList xlist
List of crossings on this particular path.
Generalized time value in the path.
Definition path.h:139
size_type path_index
Index of the path in the vector.
Definition pathvector.h:59