1 /***************************************************************************
2 * Developer: Francisco Martínez del Río (2011) *
3 * fmartin@ujaen.es *
4 * Version: 1.4.1 *
5 * *
6 * This is a public domain program *
7 ***************************************************************************/
8
9 #include "martinez.h"
10 #include "connector.h"
11 #include <algorithm>
12 #include <iostream>
13 #include <cassert>
14 #include <cstdlib>
15
16 // #define _DEBUG_ // uncomment this line if you want to debug the computation of the boolean operation
17
18 // This function is intended for debugging purposes
print(SweepEvent & e)19 void Martinez::print (SweepEvent& e)
20 {
21 const char* namesEventTypes[] = { " (NORMAL) ", " (NON_CONTRIBUTING) ", " (SAME_TRANSITION) ", " (DIFFERENT_TRANSITION) " };
22 cout << " Point: " << e.p << " Other point: " << e.other->p << (e.left ? " (Left) " : " (Right) ")
23 << (e.inside ? " (Inside) " : " (Outside) ") << (e.inOut ? " (In-Out) " : " (Out-In) ") << "Type: "
24 << namesEventTypes[e.type] << " Polygon: " << (e.pl == SUBJECT ? " (SUBJECT)" : " (CLIPPING)") << endl;
25 }
26
27 // Compare two sweep events
28 // Return true means that e1 is placed at the event queue after e2, i.e,, e1 is processed by the algorithm after e2
operator ()(SweepEvent * e1,SweepEvent * e2)29 bool Martinez::SweepEventComp::operator() (SweepEvent* e1, SweepEvent* e2) {
30 if (e1->p.x > e2->p.x) // Different x-coordinate
31 return true;
32 if (e2->p.x > e1->p.x) // Different x-coordinate
33 return false;
34 if (e1->p != e2->p) // Different points, but same x-coordinate. The event with lower y-coordinate is processed first
35 return e1->p.y > e2->p.y;
36 if (e1->left != e2->left) // Same point, but one is a left endpoint and the other a right endpoint. The right endpoint is processed first
37 return e1->left;
38 // Same point, both events are left endpoints or both are right endpoints. The event associate to the bottom segment is processed first
39 return e1->above (e2->other->p);
40 }
41
42 // e1 and a2 are the left events of line segments (e1->p, e1->other->p) and (e2->p, e2->other->p)
operator ()(SweepEvent * e1,SweepEvent * e2)43 bool Martinez::SegmentComp::operator() (SweepEvent* e1, SweepEvent* e2) {
44 if (e1 == e2)
45 return false;
46 if (signedArea (e1->p, e1->other->p, e2->p) != 0 || signedArea (e1->p, e1->other->p, e2->other->p) != 0) {
47 // Segments are not collinear
48 // If they share their left endpoint use the right endpoint to sort
49 if (e1->p == e2->p)
50 return e1->below (e2->other->p);
51
52 // Different points
53 SweepEventComp comp;
54 if (comp (e1, e2)) // has the line segment associated to e1 been inserted into S after the line segment associated to e2 ?
55 return e2->above (e1->p);
56 // The line segment associated to e2 has been inserted into S after the line segment associated to e1
57 return e1->below (e2->p);
58 }
59 // Segments are collinear. Just a consistent criterion is used
60 if (e1->p == e2->p)
61 return e1 < e2;
62 SweepEventComp comp;
63 return comp (e1, e2);
64 }
65
compute(BoolOpType op,Polygon & result)66 void Martinez::compute (BoolOpType op, Polygon& result)
67 {
68 // Test 1 for trivial result case
69 if (subject.ncontours () * clipping.ncontours () == 0) { // At least one of the polygons is empty
70 if (op == DIFFERENCE)
71 result = subject;
72 if (op == UNION || op == XOR)
73 result = (subject.ncontours () == 0) ? clipping : subject;
74 return;
75 }
76 // Test 2 for trivial result case
77 Point minsubj, maxsubj, minclip, maxclip;
78 subject.boundingbox (minsubj, maxsubj);
79 clipping.boundingbox (minclip, maxclip);
80 if (minsubj.x > maxclip.x || minclip.x > maxsubj.x || minsubj.y > maxclip.y || minclip.y > maxsubj.y) {
81 // the bounding boxes do not overlap
82 if (op == DIFFERENCE)
83 result = subject;
84 if (op == UNION || op == XOR) {
85 result = subject;
86 for (unsigned int i = 0; i < clipping.ncontours (); i++) {
87 result.push_back (Contour ());
88 result.back () = clipping.contour (i);
89 }
90 }
91 return;
92 }
93
94 // Boolean operation is not trivial
95
96 // Insert all the endpoints associated to the line segments into the event queue
97 for (unsigned int i = 0; i < subject.ncontours (); i++)
98 for (unsigned int j = 0; j < subject.contour (i).nvertices (); j++)
99 processSegment(subject.contour (i).segment (j), SUBJECT);
100 for (unsigned int i = 0; i < clipping.ncontours (); i++)
101 for (unsigned int j = 0; j < clipping.contour (i).nvertices (); j++)
102 processSegment(clipping.contour (i).segment (j), CLIPPING);
103
104 Connector connector; // to connect the edge solutions
105 set<SweepEvent*, SegmentComp> S; // Status line
106 set<SweepEvent*, SegmentComp>::iterator it, sli, prev, next;
107 SweepEvent* e;
108 const double MINMAXX = std::min (maxsubj.x, maxclip.x); // for optimization 1
109
110 while (!eq.empty()) {
111 e = eq.top ();
112 eq.pop ();
113 #ifdef _DEBUG_
114 cout << "Process event: "; print (*e);
115 #endif
116 // optimization 1
117 if ((op == INTERSECTION && (e->p.x > MINMAXX)) || (op == DIFFERENCE && e->p.x > maxsubj.x)) {
118 connector.toPolygon (result);
119 return;
120 }
121 if ((op == UNION && (e->p.x > MINMAXX))) {
122 // add all the non-processed line segments to the result
123 if (!e->left)
124 connector.add (e->segment ());
125 while (!eq.empty()) {
126 e = eq.top();
127 eq.pop();
128 if (!e->left)
129 connector.add (e->segment ());
130 }
131 connector.toPolygon (result);
132 return;
133 }
134 // end of optimization 1
135
136 if (e->left) { // the line segment must be inserted into S
137 e->poss = it = S.insert(e).first;
138 next = prev = it;
139 (prev != S.begin()) ? --prev : prev = S.end();
140
141 // Compute the inside and inOut flags
142 if (prev == S.end ()) { // there is not a previous line segment in S?
143 e->inside = e->inOut = false;
144 } else if ((*prev)->type != NORMAL) {
145 if (prev == S.begin ()) { // e overlaps with prev
146 e->inside = true; // it is not relevant to set true or false
147 e->inOut = false;
148 } else { // the previous two line segments in S are overlapping line segments
149 sli = prev;
150 sli--;
151 if ((*prev)->pl == e->pl) {
152 e->inOut = !(*prev)->inOut;
153 e->inside = !(*sli)->inOut;
154 } else {
155 e->inOut = !(*sli)->inOut;
156 e->inside = !(*prev)->inOut;
157 }
158 }
159 } else if (e->pl == (*prev)->pl) { // previous line segment in S belongs to the same polygon that "e" belongs to
160 e->inside = (*prev)->inside;
161 e->inOut = ! (*prev)->inOut;
162 } else { // previous line segment in S belongs to a different polygon that "e" belongs to
163 e->inside = ! (*prev)->inOut;
164 e->inOut = (*prev)->inside;
165 }
166
167 #ifdef _DEBUG_
168 cout << "Status line after insertion: " << endl;
169 for (set<SweepEvent*, SegmentComp>::const_iterator it2 = S.begin(); it2 != S.end(); it2++)
170 print (**it2);
171 #endif
172
173 // Process a possible intersection between "e" and its next neighbor in S
174 if ((++next) != S.end())
175 possibleIntersection(e, *next);
176
177 // Process a possible intersection between "e" and its previous neighbor in S
178 if (prev != S.end ())
179 possibleIntersection(*prev, e);
180 } else { // the line segment must be removed from S
181 next = prev = sli = e->other->poss; // S.find (e->other);
182
183 // Get the next and previous line segments to "e" in S
184 ++next;
185 (prev != S.begin()) ? --prev : prev = S.end();
186
187 // Check if the line segment belongs to the Boolean operation
188 switch (e->type) {
189 case (NORMAL):
190 switch (op) {
191 case (INTERSECTION):
192 if (e->other->inside)
193 connector.add (e->segment ());
194 break;
195 case (UNION):
196 if (!e->other->inside)
197 connector.add (e->segment ());
198 break;
199 case (DIFFERENCE):
200 if (((e->pl == SUBJECT) && (!e->other->inside)) || (e->pl == CLIPPING && e->other->inside))
201 connector.add (e->segment ());
202 break;
203 case (XOR):
204 connector.add (e->segment ());
205 break;
206 }
207 break;
208 case (SAME_TRANSITION):
209 if (op == INTERSECTION || op == UNION)
210 connector.add (e->segment ());
211 break;
212 case (DIFFERENT_TRANSITION):
213 if (op == DIFFERENCE)
214 connector.add (e->segment ());
215 break;
216 }
217 // delete line segment associated to e from S and check for intersection between the neighbors of "e" in S
218 S.erase (sli);
219 if (next != S.end() && prev != S.end())
220 possibleIntersection (*prev, *next);
221 }
222
223 #ifdef _DEBUG_
224 cout << "Status line after processing intersections: " << endl;
225 for (set<SweepEvent*, SegmentComp>::const_iterator it2 = S.begin(); it2 != S.end(); it2++)
226 print (**it2);
227 cout << endl;
228 #endif
229 }
230 connector.toPolygon (result);
231 }
232
processSegment(const Segment & s,PolygonType pl)233 void Martinez::processSegment (const Segment& s, PolygonType pl)
234 {
235 if (s.begin () == s.end ()) // if the two edge endpoints are equal the segment is dicarded
236 return; // in the future this can be done as preprocessing to avoid "polygons" with less than 3 edges
237 SweepEvent* e1 = storeSweepEvent (SweepEvent(s.begin(), true, pl, 0));
238 SweepEvent* e2 = storeSweepEvent (SweepEvent(s.end(), true, pl, e1));
239 e1->other = e2;
240
241 if (e1->p.x < e2->p.x) {
242 e2->left = false;
243 } else if (e1->p.x > e2->p.x) {
244 e1->left = false;
245 } else if (e1->p.y < e2->p.y) { // the line segment is vertical. The bottom endpoint is the left endpoint
246 e2->left = false;
247 } else {
248 e1->left = false;
249 }
250 eq.push (e1);
251 eq.push (e2);
252 }
253
possibleIntersection(SweepEvent * e1,SweepEvent * e2)254 void Martinez::possibleIntersection (SweepEvent* e1, SweepEvent* e2)
255 {
256 // if ((e1->pl == e2->pl) ) // you can uncomment these two lines if self-intersecting polygons are not allowed
257 // return false;
258
259 Point ip1, ip2; // intersection points
260 int nintersections;
261
262 if (!(nintersections = findIntersection(e1->segment (), e2->segment (), ip1, ip2)))
263 return;
264
265 if ((nintersections == 1) && ((e1->p == e2->p) || (e1->other->p == e2->other->p)))
266 return; // the line segments intersect at an endpoint of both line segments
267
268 if (nintersections == 2 && e1->pl == e2->pl) { // the line segments overlap, but they belong to the same polygon
269 std::cerr << "A polygon has overlapping edges. Sorry, but the program does not work yet with this kind of polygon\n";
270 exit (1);
271 }
272
273 // The line segments associated to e1 and e2 intersect
274 nint += nintersections;
275
276 if (nintersections == 1) {
277 if (e1->p != ip1 && e1->other->p != ip1) // if ip1 is not an endpoint of the line segment associated to e1 then divide "e1"
278 divideSegment (e1, ip1);
279 if (e2->p != ip1 && e2->other->p != ip1) // if ip1 is not an endpoint of the line segment associated to e2 then divide "e2"
280 divideSegment (e2, ip1);
281 return;
282 }
283
284 // The line segments overlap
285 vector<SweepEvent *> sortedEvents;
286 if (e1->p == e2->p) {
287 sortedEvents.push_back (0);
288 } else if (sec (e1, e2)) {
289 sortedEvents.push_back (e2);
290 sortedEvents.push_back (e1);
291 } else {
292 sortedEvents.push_back (e1);
293 sortedEvents.push_back (e2);
294 }
295 if (e1->other->p == e2->other->p) {
296 sortedEvents.push_back (0);
297 } else if (sec (e1->other, e2->other)) {
298 sortedEvents.push_back (e2->other);
299 sortedEvents.push_back (e1->other);
300 } else {
301 sortedEvents.push_back (e1->other);
302 sortedEvents.push_back (e2->other);
303 }
304
305 if (sortedEvents.size () == 2) { // are both line segments equal?
306 e1->type = e1->other->type = NON_CONTRIBUTING;
307 e2->type = e2->other->type = (e1->inOut == e2->inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION;
308 return;
309 }
310 if (sortedEvents.size () == 3) { // the line segments share an endpoint
311 sortedEvents[1]->type = sortedEvents[1]->other->type = NON_CONTRIBUTING;
312 if (sortedEvents[0]) // is the right endpoint the shared point?
313 sortedEvents[0]->other->type = (e1->inOut == e2->inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION;
314 else // the shared point is the left endpoint
315 sortedEvents[2]->other->type = (e1->inOut == e2->inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION;
316 divideSegment (sortedEvents[0] ? sortedEvents[0] : sortedEvents[2]->other, sortedEvents[1]->p);
317 return;
318 }
319 if (sortedEvents[0] != sortedEvents[3]->other) { // no line segment includes totally the other one
320 sortedEvents[1]->type = NON_CONTRIBUTING;
321 sortedEvents[2]->type = (e1->inOut == e2->inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION;
322 divideSegment (sortedEvents[0], sortedEvents[1]->p);
323 divideSegment (sortedEvents[1], sortedEvents[2]->p);
324 return;
325 }
326 // one line segment includes the other one
327 sortedEvents[1]->type = sortedEvents[1]->other->type = NON_CONTRIBUTING;
328 divideSegment (sortedEvents[0], sortedEvents[1]->p);
329 sortedEvents[3]->other->type = (e1->inOut == e2->inOut) ? SAME_TRANSITION : DIFFERENT_TRANSITION;
330 divideSegment (sortedEvents[3]->other, sortedEvents[2]->p);
331 }
332
divideSegment(SweepEvent * e,const Point & p)333 void Martinez::divideSegment (SweepEvent* e, const Point& p)
334 {
335 // "Right event" of the "left line segment" resulting from dividing e (the line segment associated to e)
336 SweepEvent *r = storeSweepEvent(SweepEvent(p, false, e->pl, e, e->type));
337 // "Left event" of the "right line segment" resulting from dividing e (the line segment associated to e)
338 SweepEvent *l = storeSweepEvent(SweepEvent(p, true, e->pl, e->other, e->other->type));
339 if (sec (l, e->other)) { // avoid a rounding error. The left event would be processed after the right event
340 cout << "Oops" << endl;
341 e->other->left = true;
342 l->left = false;
343 }
344 if (sec (e, r)) { // avoid a rounding error. The left event would be processed after the right event
345 cout << "Oops2" << endl;
346 // cout << *e << endl;
347 }
348 e->other->other = l;
349 e->other = r;
350 eq.push(l);
351 eq.push(r);
352 }
353