1 // Copyright 2013 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS-IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 // Original author: ericv@google.com (Eric Veach)
16 // Converted to D:  madric@gmail.com (Vijay Nayar)
17 
18 module s2.shapeutil.visit_crossing_edge_pairs;
19 
20 import s2.logger;
21 import s2.s2cell_id;
22 import s2.s2crossing_edge_query : CrossingType;
23 import s2.s2crossing_edge_query;
24 import s2.s2edge_crosser;
25 import s2.s2error;
26 import s2.s2padded_cell;
27 import s2.s2point;
28 import s2.s2shape;
29 import s2.s2shape_index;
30 import s2.s2wedge_relations : getWedgeRelation, WedgeRelation;
31 import s2.shapeutil.range_iterator;
32 import s2.shapeutil.shape_edge;
33 
34 
35 /**
36  * A function that is called with pairs of crossing edges.  The function may
37  * return false in order to request that the algorithm should be terminated,
38  * i.e. no further crossings are needed.
39  *
40  * "is_interior" indicates that the crossing is at a point interior to both
41  * edges (i.e., not at a vertex).  (The calling function already has this
42  * information and it is moderately expensive to recompute.)
43  */
44 alias EdgePairVisitor = bool delegate(in ShapeEdge a, in ShapeEdge b, bool isInterior);
45 
46 /**
47  * Visits all pairs of crossing edges in the given S2ShapeIndex, terminating
48  * early if the given EdgePairVisitor function returns false (in which case
49  * VisitCrossings returns false as well).  "type" indicates whether all
50  * crossings should be visited, or only interior crossings.
51  *
52  * CAVEAT: Crossings may be visited more than once.
53  */
54 bool visitCrossingEdgePairs(
55     S2ShapeIndex index, CrossingType type, EdgePairVisitor visitor) {
56   bool need_adjacent = (type == CrossingType.ALL);
57   return visitCrossings(index, type, need_adjacent, visitor);
58 }
59 
60 /**
61  * Like the above, but visits all pairs of crossing edges where one edge comes
62  * from each S2ShapeIndex.
63  *
64  * CAVEAT: Crossings may be visited more than once.
65  */
66 bool visitCrossingEdgePairs(
67     S2ShapeIndex a_index, S2ShapeIndex b_index, CrossingType type, EdgePairVisitor visitor) {
68   // We look for S2CellId ranges where the indexes of A and B overlap, and
69   // then test those edges for crossings.
70 
71   // TODO(ericv): Use brute force if the total number of edges is small enough
72   // (using a larger threshold if the S2ShapeIndex is not constructed yet).
73   auto ai = new RangeIterator(a_index);
74   auto bi = new RangeIterator(b_index);
75   auto ab = new IndexCrosser(a_index, b_index, type, visitor, false);  // Tests A against B
76   auto ba = new IndexCrosser(b_index, a_index, type, visitor, true);   // Tests B against A
77   while (!ai.done() || !bi.done()) {
78     if (ai.rangeMax() < bi.rangeMin()) {
79       // The A and B cells don't overlap, and A precedes B.
80       ai.seekTo(bi);
81     } else if (bi.rangeMax() < ai.rangeMin()) {
82       // The A and B cells don't overlap, and B precedes A.
83       bi.seekTo(ai);
84     } else {
85       // One cell contains the other.  Determine which cell is larger.
86       long ab_relation = ai.id().lsb() - bi.id().lsb();
87       if (ab_relation > 0) {
88         // A's index cell is larger.
89         if (!ab.visitCrossings(ai, bi)) return false;
90       } else if (ab_relation < 0) {
91         // B's index cell is larger.
92         if (!ba.visitCrossings(bi, ai)) return false;
93       } else {
94         // The A and B cells are the same.
95         if (ai.cell().numEdges() > 0 && bi.cell().numEdges() > 0) {
96           if (!ab.visitCellCellCrossings(ai.cell(), bi.cell())) return false;
97         }
98         ai.next();
99         bi.next();
100       }
101     }
102   }
103   return true;
104 }
105 
106 // IndexCrosser is a helper class for finding the edge crossings between a
107 // pair of S2ShapeIndexes.  It is instantiated twice, once for the index pair
108 // (A,B) and once for the index pair (B,A), in order to be able to test edge
109 // crossings in the most efficient order.
110 // TODO: Resume here.
111 class IndexCrosser {
112  public:
113   // If "swapped" is true, the loops A and B have been swapped.  This affects
114   // how arguments are passed to the given loop relation, since for example
115   // A.Contains(B) is not the same as B.Contains(A).
116   this(S2ShapeIndex a_index, S2ShapeIndex b_index,
117       CrossingType type, EdgePairVisitor visitor, bool swapped) {
118     _aIndex = a_index;
119     _bIndex = b_index;
120     _visitor = visitor;
121     _minCrossingSign = type == CrossingType.INTERIOR ? 1 : 0;
122     _swapped = swapped;
123     _bQuery = new S2CrossingEdgeQuery(_bIndex);
124   }
125 
126   // Given two iterators positioned such that ai->id().Contains(bi->id()),
127   // visits all crossings between edges of A and B that intersect a->id().
128   // Terminates early and returns false if visitor_ returns false.
129   // Advances both iterators past ai->id().
130   bool visitCrossings(RangeIterator ai, RangeIterator bi) {
131     logger.logDebug(!ai.id().contains(bi.id()), " ai must contain bi");
132     if (ai.cell().numEdges() == 0) {
133       // Skip over the cells of B using binary search.
134       bi.seekBeyond(ai);
135     } else {
136       // If ai->id() intersects many edges of B, then it is faster to use
137       // S2CrossingEdgeQuery to narrow down the candidates.  But if it
138       // intersects only a few edges, it is faster to check all the crossings
139       // directly.  We handle this by advancing "bi" and keeping track of how
140       // many edges we would need to test.
141       enum int kEdgeQueryMinEdges = 23;
142       int b_edges = 0;
143       _bCells.length = 0;
144       do {
145         int cell_edges = bi.cell().numEdges();
146         if (cell_edges > 0) {
147           b_edges += cell_edges;
148           if (b_edges >= kEdgeQueryMinEdges) {
149             // There are too many edges, so use an S2CrossingEdgeQuery.
150             if (!visitSubcellCrossings(ai.cell(), ai.id())) return false;
151             bi.seekBeyond(ai);
152             return true;
153           }
154           _bCells ~= bi.cell();
155         }
156         bi.next();
157       } while (bi.id() <= ai.rangeMax());
158       if (_bCells.length != 0) {
159         // Test all the edge crossings directly.
160         getShapeEdges(_aIndex, ai.cell(), _aShapeEdges);
161         getShapeEdges(_bIndex, _bCells, _bShapeEdges);
162         if (!visitEdgesEdgesCrossings(_aShapeEdges, _bShapeEdges)) {
163           return false;
164         }
165       }
166     }
167     ai.next();
168     return true;
169   }
170 
171   // Given two index cells, visits all crossings between edges of those cells.
172   // Terminates early and returns false if visitor_ returns false.
173   bool visitCellCellCrossings(in S2ShapeIndexCell a_cell, in S2ShapeIndexCell b_cell) {
174     // Test all edges of "a_cell" against all edges of "b_cell".
175     getShapeEdges(_aIndex, a_cell, _aShapeEdges);
176     getShapeEdges(_bIndex, b_cell, _bShapeEdges);
177     return visitEdgesEdgesCrossings(_aShapeEdges, _bShapeEdges);
178   }
179 
180  private:
181   bool visitEdgePair(in ShapeEdge a, in ShapeEdge b, bool is_interior) {
182     if (_swapped) {
183       return _visitor(b, a, is_interior);
184     } else {
185       return _visitor(a, b, is_interior);
186     }
187   }
188 
189   // Visits all crossings of the current edge with all edges of the given index
190   // cell of B.  Terminates early and returns false if visitor_ returns false.
191   bool visitEdgeCellCrossings(in ShapeEdge a, in S2ShapeIndexCell b_cell) {
192     // Test the current edge of A against all edges of "b_cell".
193 
194     // Note that we need to use a new S2EdgeCrosser (or call Init) whenever we
195     // replace the contents of b_shape_edges_, since S2EdgeCrosser requires that
196     // its S2Point arguments point to values that persist between Init() calls.
197     getShapeEdges(_bIndex, b_cell, _bShapeEdges);
198     auto crosser = new S2CopyingEdgeCrosser(a.v0(), a.v1());
199     foreach (const ShapeEdge b; _bShapeEdges) {
200       if (crosser.c() != b.v0()) {
201         crosser.restartAt(b.v0());
202       }
203       int sign = crosser.crossingSign(b.v1());
204       if (sign >= _minCrossingSign) {
205         if (!visitEdgePair(a, b, sign == 1)) return false;
206       }
207     }
208     return true;
209   }
210 
211   // Visits all crossings of any edge in "a_cell" with any index cell of B that
212   // is a descendant of "b_id".  Terminates early and returns false if
213   // visitor_ returns false.
214   bool visitSubcellCrossings(in S2ShapeIndexCell a_cell, S2CellId b_id) {
215     // Test all edges of "a_cell" against the edges contained in B index cells
216     // that are descendants of "b_id".
217     getShapeEdges(_aIndex, a_cell, _aShapeEdges);
218     auto b_root = new S2PaddedCell(b_id, 0);
219     foreach (const ShapeEdge a; _aShapeEdges) {
220       // Use an S2CrossingEdgeQuery starting at "b_root" to find the index cells
221       // of B that might contain crossing edges.
222       if (!_bQuery.visitCells(
223               a.v0(), a.v1(), b_root,
224               (const S2ShapeIndexCell cell)  => visitEdgeCellCrossings(a, cell))) {
225         return false;
226       }
227     }
228     return true;
229   }
230 
231   // Visits all crossings of any edge in "a_edges" with any edge in "b_edges".
232   bool visitEdgesEdgesCrossings(
233       in ShapeEdgeVector a_edges, in ShapeEdgeVector b_edges) {
234     // Test all edges of "a_edges" against all edges of "b_edges".
235     foreach (const ShapeEdge a; a_edges) {
236       auto crosser = new S2EdgeCrosser(a.v0(), a.v1());
237       foreach (const ShapeEdge b; b_edges) {
238         if (crosser.c() is null || *crosser.c() != b.v0()) {
239           crosser.restartAt(b.v0());
240         }
241         int sign = crosser.crossingSign(b.v1());
242         if (sign >= _minCrossingSign) {
243           if (!visitEdgePair(a, b, sign == 1)) return false;
244         }
245       }
246     }
247     return true;
248   }
249 
250   S2ShapeIndex _aIndex;
251   S2ShapeIndex _bIndex;
252   const EdgePairVisitor _visitor;
253   const int _minCrossingSign;
254   const bool _swapped;
255 
256   // Temporary data declared here to avoid repeated memory allocations.
257   S2CrossingEdgeQuery _bQuery;
258   const(S2ShapeIndexCell)[] _bCells;
259   ShapeEdgeVector _aShapeEdges;
260   ShapeEdgeVector _bShapeEdges;
261 }
262 
263 // Given an S2ShapeIndex containing a single polygonal shape (e.g., an
264 // S2Polygon or S2Loop), return true if any loop has a self-intersection
265 // (including duplicate vertices) or crosses any other loop (including vertex
266 // crossings and duplicate edges) and set "error" to a human-readable error
267 // message.  Otherwise return false and leave "error" unchanged.
268 //
269 // This method is used to implement the FindValidationError methods of S2Loop
270 // and S2Polygon.
271 //
272 // TODO(ericv): Add an option to support S2LaxPolygonShape rules (i.e.,
273 // duplicate vertices and edges are allowed, but loop crossings are not).
274 bool findSelfIntersection(S2ShapeIndex index, ref S2Error error) {
275   if (index.numShapeIds() == 0) return false;
276   if (index.numShapeIds() != 1)
277     logger.logError("index.numShapeIds()=", index.numShapeIds(), ", expected 1.");
278   const(S2Shape) shape = index.shape(0);
279 
280   // Visit all crossing pairs except possibly for ones of the form (AB, BC),
281   // since such pairs are very common and FindCrossingError() only needs pairs
282   // of the form (AB, AC).
283   return !visitCrossings(
284       index, CrossingType.ALL, false /*need_adjacent*/,
285       (in ShapeEdge a, in ShapeEdge b, bool isInterior) =>
286           !findCrossingError(shape, a, b, isInterior, error));
287 }
288 
289 // Ensure that we don't usually need to allocate memory when collecting the
290 // edges in an S2ShapeIndex cell (which by default have about 10 edges).
291 // TODO(vnayar): Investigate if it's worth it to avoid dynamic arrays.
292 alias ShapeEdgeVector = ShapeEdge[];
293 
294 // Given a vector of edges within an S2ShapeIndexCell, visit all pairs of
295 // crossing edges (of the given CrossingType).
296 private bool visitCrossings(
297     in ShapeEdgeVector shape_edges, CrossingType type, bool need_adjacent,
298     EdgePairVisitor visitor) {
299   const int min_crossing_sign = (type == CrossingType.INTERIOR) ? 1 : 0;
300   size_t num_edges = shape_edges.length;
301   for (int i = 0; i + 1 < num_edges; ++i) {
302     const ShapeEdge a = shape_edges[i];
303     int j = i + 1;
304     // A common situation is that an edge AB is followed by an edge BC.  We
305     // only need to visit such crossings if "need_adjacent" is true (even if
306     // AB and BC belong to different edge chains).
307     if (!need_adjacent && a.v1() == shape_edges[j].v0()) {
308       if (++j >= num_edges) break;
309     }
310     auto crosser = new S2CopyingEdgeCrosser(a.v0(), a.v1());
311     for (; j < num_edges; ++j) {
312       const ShapeEdge b = shape_edges[j];
313       if (crosser.c() != b.v0()) {
314         crosser.restartAt(b.v0());
315       }
316       int sign = crosser.crossingSign(b.v1());
317       if (sign >= min_crossing_sign) {
318         if (!visitor(a, b, sign == 1)) return false;
319       }
320     }
321   }
322   return true;
323 }
324 
325 // Visits all pairs of crossing edges in the given S2ShapeIndex, terminating
326 // early if the given EdgePairVisitor function returns false (in which case
327 // VisitCrossings returns false as well).  "type" indicates whether all
328 // crossings should be visited, or only interior crossings.
329 //
330 // If "need_adjacent" is false, then edge pairs of the form (AB, BC) may
331 // optionally be ignored (even if the two edges belong to different edge
332 // chains).  This option exists for the benefit of FindSelfIntersection(),
333 // which does not need such edge pairs (see below).
334 static bool visitCrossings(
335     S2ShapeIndex index, CrossingType type, bool need_adjacent, in EdgePairVisitor visitor) {
336   // TODO(ericv): Use brute force if the total number of edges is small enough
337   // (using a larger threshold if the S2ShapeIndex is not constructed yet).
338   ShapeEdgeVector shape_edges;
339   for (auto it = new S2ShapeIndex.Iterator(index, S2ShapeIndex.InitialPosition.BEGIN);
340        !it.done(); it.next()) {
341     getShapeEdges(index, it.cell(), shape_edges);
342     if (!visitCrossings(shape_edges, type, need_adjacent, visitor)) {
343       return false;
344     }
345   }
346   return true;
347 }
348 
349 // Returns a vector containing all edges in the given S2ShapeIndexCell.
350 // (The result is returned as an output parameter so that the same storage can
351 // be reused, rather than allocating a new temporary vector each time.)
352 private void getShapeEdges(in S2ShapeIndex index, in S2ShapeIndexCell cell,
353     ref ShapeEdgeVector shape_edges) {
354   shape_edges.length = 0;
355   appendShapeEdges(index, cell, shape_edges);
356 }
357 
358 // Returns a vector containing all edges in the given S2ShapeIndexCell vector.
359 // (The result is returned as an output parameter so that the same storage can
360 // be reused, rather than allocating a new temporary vector each time.)
361 private void getShapeEdges(in S2ShapeIndex index, in S2ShapeIndexCell[] cells,
362     ref ShapeEdgeVector shape_edges) {
363   shape_edges.length = 0;
364   for (int c = 0; c < cells.length; ++c) {
365     appendShapeEdges(index, cells[c], shape_edges);
366   }
367 }
368 
369 
370 // Given two loop edges that cross (including at a shared vertex), return true
371 // if there is a crossing error and set "error" to a human-readable message.
372 private bool findCrossingError(
373     in S2Shape shape, in ShapeEdge a, in ShapeEdge b, bool is_interior, ref S2Error error) {
374   bool is_polygon = shape.numChains() > 1;
375   S2Shape.ChainPosition ap = shape.chainPosition(a.id().edgeId);
376   S2Shape.ChainPosition bp = shape.chainPosition(b.id().edgeId);
377   if (is_interior) {
378     if (ap.chainId != bp.chainId) {
379       error.initialize(S2Error.Code.POLYGON_LOOPS_CROSS,
380           "Loop %d edge %d crosses loop %d edge %d",
381           ap.chainId, ap.offset, bp.chainId, bp.offset);
382     } else {
383       initLoopError(S2Error.Code.LOOP_SELF_INTERSECTION,
384                     "Edge %d crosses edge %d", ap, bp, is_polygon, error);
385     }
386     return true;
387   }
388   // Loops are not allowed to have duplicate vertices, and separate loops
389   // are not allowed to share edges or cross at vertices.  We only need to
390   // check a given vertex once, so we also require that the two edges have
391   // the same end vertex.
392   if (a.v1() != b.v1()) return false;
393   if (ap.chainId == bp.chainId) {
394     initLoopError(S2Error.Code.DUPLICATE_VERTICES,
395         "Edge %d has duplicate vertex with edge %d",
396         ap, bp, is_polygon, error);
397     return true;
398   }
399   int a_len = shape.chain(ap.chainId).length;
400   int b_len = shape.chain(bp.chainId).length;
401   int a_next = (ap.offset + 1 == a_len) ? 0 : ap.offset + 1;
402   int b_next = (bp.offset + 1 == b_len) ? 0 : bp.offset + 1;
403   S2Point a2 = shape.chainEdge(ap.chainId, a_next).v1;
404   S2Point b2 = shape.chainEdge(bp.chainId, b_next).v1;
405   if (a.v0() == b.v0() || a.v0() == b2) {
406     // The second edge index is sometimes off by one, hence "near".
407     error.initialize(S2Error.Code.POLYGON_LOOPS_SHARE_EDGE,
408         "Loop %d edge %d has duplicate near loop %d edge %d",
409         ap.chainId, ap.offset, bp.chainId, bp.offset);
410     return true;
411   }
412   // Since S2ShapeIndex loops are oriented such that the polygon interior is
413   // always on the left, we need to handle the case where one wedge contains
414   // the complement of the other wedge.  This is not specifically detected by
415   // GetWedgeRelation, so there are two cases to check for.
416   //
417   // Note that we don't need to maintain any state regarding loop crossings
418   // because duplicate edges are detected and rejected above.
419   if (getWedgeRelation(a.v0(), a.v1(), a2, b.v0(), b2) == WedgeRelation.WEDGE_PROPERLY_OVERLAPS
420       && getWedgeRelation(a.v0(), a.v1(), a2, b2, b.v0())
421           == WedgeRelation.WEDGE_PROPERLY_OVERLAPS) {
422     error.initialize(S2Error.Code.POLYGON_LOOPS_CROSS,
423         "Loop %d edge %d crosses loop %d edge %d",
424         ap.chainId, ap.offset, bp.chainId, bp.offset);
425     return true;
426   }
427   return false;
428 }
429 
430 // Appends all edges in the given S2ShapeIndexCell to the given vector.
431 private void appendShapeEdges(in S2ShapeIndex index,
432                              in S2ShapeIndexCell cell,
433                              ref ShapeEdgeVector shape_edges) {
434   for (int s = 0; s < cell.numClipped(); ++s) {
435     const S2ClippedShape clipped = cell.clipped(s);
436     const S2Shape shape = index.shape(clipped.shapeId());
437     int num_edges = clipped.numEdges();
438     for (int i = 0; i < num_edges; ++i) {
439       shape_edges ~= new ShapeEdge(shape, clipped.edge(i));
440     }
441   }
442 }
443 
444 // Helper function that formats a loop error message.  If the loop belongs to
445 // a multi-loop polygon, adds a prefix indicating which loop is affected.
446 private void initLoopError(S2Error.Code code, string format,
447                           S2Shape.ChainPosition ap, S2Shape.ChainPosition bp,
448                           bool is_polygon, ref S2Error error) {
449   error.initialize(code, format, ap.offset, bp.offset);
450   if (is_polygon) {
451     error.initialize(code, "Loop %d: %s", ap.chainId, error.text());
452   }
453 }