@@ -44,36 +44,158 @@ function _union(
44
44
ext_a = GI. getexterior (poly_a)
45
45
ext_b = GI. getexterior (poly_b)
46
46
# Then, I get the union of the exteriors
47
- a_list, b_list, a_idx_list = _build_ab_list (T, ext_a, ext_b)
48
- polys = _trace_polynodes (T, a_list, b_list, a_idx_list, (x, y) -> x ? ( - 1 ) : 1 )
47
+ a_list, b_list, a_idx_list = _build_ab_list (T, ext_a, ext_b, _union_delay_cross_f, _union_delay_bounce_f )
48
+ polys = _trace_polynodes (T, a_list, b_list, a_idx_list, _union_step )
49
49
n_pieces = length (polys)
50
- # Check if one polygon totally within other and if so, return the larger polygon.
50
+ # Check if one polygon totally within other and if so, return the larger polygon
51
+ a_in_b, b_in_a = false , false
51
52
if n_pieces == 0 # no crossing points, determine if either poly is inside the other
52
53
a_in_b, b_in_a = _find_non_cross_orientation (a_list, b_list, ext_a, ext_b)
53
54
if a_in_b
54
55
push! (polys, GI. Polygon ([tuples (ext_b)]))
55
56
elseif b_in_a
56
57
push! (polys, GI. Polygon ([tuples (ext_a)]))
57
58
else
58
- share_edge_warn (a_list, " Edge case: polygons share edge but can't be combined." ) # will get taken care of with "glued edges"
59
59
push! (polys, tuples (poly_a))
60
60
push! (polys, tuples (poly_b))
61
61
return polys
62
62
end
63
- elseif n_pieces > 1 # extra polygons are holes (n_pieces == 1 is the desired state)
64
- sort! (polys, by = area, rev = true ) # sort so first element is the exterior
63
+ elseif n_pieces > 1
64
+ #= extra polygons are holes (n_pieces == 1 is the desired state) and since
65
+ holes are formed by regions exterior to both poly_a and poly_b, they can't interact
66
+ with pre-existing holes =#
67
+ sort! (polys, by = area, rev = true ) # sort by area so first element is the exterior
68
+ # the first element is the exterior, the rest are holes
69
+ @views append! (polys[1 ]. geom, (GI. getexterior (p) for p in polys[2 : end ]))
70
+ keepat! (polys, 1 )
65
71
end
66
- # the first element is the exterior, the rest are holes
67
- new_holes = @views ( GI. getexterior (p) for p in polys[ 2 : end ])
68
- polys = n_pieces > 1 ? polys[ 1 : 1 ] : polys
69
- # Add holes back in for there are any
70
- if GI . nhole (poly_a) != 0 || GI . nhole (poly_b) != 0 || n_pieces > 1
71
- hole_iterator = Iterators . flatten ((GI . gethole (poly_a), GI . gethole (poly_b), new_holes))
72
- _add_holes_to_polys! (T, polys, hole_iterator )
72
+ # Add in holes
73
+ if GI. nhole (poly_a) != 0 || GI . nhole (poly_b) != 0
74
+ _add_union_holes! (polys, a_in_b, b_in_a, poly_a, poly_b)
75
+ end
76
+ # Remove uneeded collinear points on same edge
77
+ for p in polys
78
+ _remove_collinear_points! (p, [ false ] )
73
79
end
74
80
return polys
75
81
end
76
82
83
+ # # Helper functions for Unions with Greiner and Hormann Polygon Clipping
84
+
85
+ #= When marking the crossing status of a delayed crossing, the chain start point is crossing
86
+ when the start point is a entry point and is a bouncing point when the start point is an
87
+ exit point. The end of the chain has the opposite crossing / bouncing status. =#
88
+ _union_delay_cross_f (x) = (x, ! x)
89
+
90
+ #= When marking the crossing status of a delayed bouncing, the chain start and end points
91
+ are bouncing if the current polygon's adjacent edges are within the non-tracing polygon. If
92
+ the edges are outside then the chain endpoints are marked as crossing. x is a boolean
93
+ representing if the edges are inside or outside of the polygon. =#
94
+ _union_delay_bounce_f (x, _) = ! x
95
+
96
+ #= When tracing polygons, step backwards if the most recent intersection point was an entry
97
+ point, else step forwards where x is the entry/exit status. =#
98
+ _union_step (x, _) = x ? (- 1 ) : 1
99
+
100
+ #= Add holes from two polygons to the exterior polygon formed by their union. If adding the
101
+ the holes reveals that the polygons aren't actually intersecting, return the original
102
+ polygons. =#
103
+ function _add_union_holes! (polys, a_in_b, b_in_a, poly_a, poly_b)
104
+ if a_in_b
105
+ _add_union_holes_contained_polys! (polys, poly_a, poly_b)
106
+ elseif b_in_a
107
+ _add_union_holes_contained_polys! (polys, poly_b, poly_a)
108
+ else # Polygons intersect, but neither is contained in the other
109
+ n_a_holes = GI. nhole (poly_a)
110
+ ext_poly_a = GI. Polygon (StaticArrays. SVector (GI. getexterior (poly_a)))
111
+ ext_poly_b = GI. Polygon (StaticArrays. SVector (GI. getexterior (poly_b)))
112
+ #= Start with poly_b when comparing with holes from poly_a and then switch to poly_a
113
+ to compare with holes from poly_b. For current_poly, use ext_poly_b to avoid
114
+ repeating overlapping holes in poly_a and poly_b =#
115
+ curr_exterior_poly = n_a_holes > 0 ? ext_poly_b : ext_poly_a
116
+ current_poly = n_a_holes > 0 ? ext_poly_b : poly_a
117
+ # Loop over all holes in both original polygons
118
+ for (i, ih) in enumerate (Iterators. flatten ((GI. gethole (poly_a), GI. gethole (poly_b))))
119
+ in_ext, _, _ = _line_polygon_interactions (ih, curr_exterior_poly; closed_line = true )
120
+ if ! in_ext
121
+ #= if the hole isn't in the overlapping region between the two polygons, add
122
+ the hole to the resulting polygon as we know it can't interact with any
123
+ other holes =#
124
+ push! (polys[1 ]. geom, ih)
125
+ else
126
+ #= if the hole is at least partially in the overlapping region, take the
127
+ difference of the hole from the polygon it didn't originate from - note that
128
+ when current_poly is poly_a this includes poly_a holes so overlapping holes
129
+ between poly_a and poly_b within the overlap are added, in addition to all
130
+ holes in non-overlapping regions =#
131
+ h_poly = GI. Polygon (StaticArrays. SVector (ih))
132
+ new_holes = difference (h_poly, current_poly; target = GI. PolygonTrait ())
133
+ append! (polys[1 ]. geom, (GI. getexterior (new_h) for new_h in new_holes))
134
+ end
135
+ if i == n_a_holes
136
+ curr_exterior_poly = ext_poly_a
137
+ current_poly = poly_a
138
+ end
139
+ end
140
+ end
141
+ return
142
+ end
143
+
144
+ #= Add holes holes to the union of two polygons where one of the original polygons was
145
+ inside of the other. If adding the the holes reveal that the polygons aren't actually
146
+ intersecting, return the original polygons.=#
147
+ function _add_union_holes_contained_polys! (polys, interior_poly, exterior_poly)
148
+ union_poly = polys[1 ]
149
+ ext_int_ring = GI. getexterior (interior_poly)
150
+ for (i, ih) in enumerate (GI. gethole (exterior_poly))
151
+ poly_ih = GI. Polygon (StaticArrays. SVector (ih))
152
+ in_ih, on_ih, out_ih = _line_polygon_interactions (ext_int_ring, poly_ih; closed_line = true )
153
+ if in_ih # at least part of interior polygon exterior is within the ith hole
154
+ if ! on_ih && ! out_ih
155
+ #= interior polygon is completly within the ith hole - polygons aren't
156
+ touching and do not actually form a union =#
157
+ polys[1 ] = tuples (interior_poly)
158
+ push! (polys, tuples (exterior_poly))
159
+ return polys
160
+ else
161
+ #= interior polygon is partially within the ith hole - area of interior
162
+ polygon reduces the size of the hole =#
163
+ new_holes = difference (poly_ih, interior_poly; target = GI. PolygonTrait ())
164
+ append! (union_poly. geom, (GI. getexterior (new_h) for new_h in new_holes))
165
+ end
166
+ else # none of interior polygon exterior is within the ith hole
167
+ if ! out_ih
168
+ #= interior polygon's exterior is the same as the ith hole - polygons do
169
+ form a union, but do not overlap so all holes stay in final polygon =#
170
+ append! (union_poly. geom, Iterators. drop (GI. gethole (exterior_poly), i))
171
+ append! (union_poly. geom, GI. gethole (interior_poly))
172
+ return polys
173
+ else
174
+ #= interior polygon's exterior is outside of the ith hole - the interior
175
+ polygon could either be disjoint from the hole, or contain the hole =#
176
+ ext_int_poly = GI. Polygon (StaticArrays. SVector (ext_int_ring))
177
+ in_int, _, _ = _line_polygon_interactions (ih, ext_int_poly; closed_line = true )
178
+ if in_int
179
+ #= interior polygon contains the hole - overlapping holes between the
180
+ interior and exterior polygons will be added =#
181
+ for jh in GI. gethole (interior_poly)
182
+ poly_jh = GI. Polygon (StaticArrays. SVector (jh))
183
+ if intersects (poly_ih, poly_jh)
184
+ new_holes = intersection (poly_ih, poly_jh; target = GI. PolygonTrait ())
185
+ append! (union_poly. geom, (GI. getexterior (new_h) for new_h in new_holes))
186
+ end
187
+ end
188
+ else
189
+ #= interior polygon and the exterior polygon are disjoint - add the ith
190
+ hole as it is not covered by the interior polygon =#
191
+ push! (union_poly. geom, ih)
192
+ end
193
+ end
194
+ end
195
+ end
196
+ return
197
+ end
198
+
77
199
78
200
# Many type and target combos aren't implemented
79
201
function _union (
0 commit comments