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 }