|
| 1 | +package s2 |
| 2 | + |
| 3 | +import "fmt" |
| 4 | + |
| 5 | +type ShapeEdgeVector []ShapeEdge |
| 6 | + |
| 7 | +type EdgePairVisitor func(a, b ShapeEdge, isInterior bool) bool |
| 8 | + |
| 9 | +// getShapeEdges returns all edges in the given S2ShapeIndexCell. |
| 10 | +func getShapeEdges(index *ShapeIndex, cell *ShapeIndexCell) ShapeEdgeVector { |
| 11 | + var shapeEdges ShapeEdgeVector |
| 12 | + for _, clipped := range cell.shapes { |
| 13 | + shape := index.Shape(clipped.shapeID) |
| 14 | + for _, edgeID := range clipped.edges { |
| 15 | + shapeEdges = append(shapeEdges, ShapeEdge{ |
| 16 | + ID: ShapeEdgeID{ |
| 17 | + ShapeID: clipped.shapeID, |
| 18 | + EdgeID: int32(edgeID), |
| 19 | + }, |
| 20 | + Edge: shape.Edge(edgeID), |
| 21 | + }) |
| 22 | + } |
| 23 | + } |
| 24 | + return shapeEdges |
| 25 | +} |
| 26 | + |
| 27 | +// VisitCrossings finds and processes all crossing edge pairs. |
| 28 | +func visitCrossings(shapeEdges ShapeEdgeVector, crossingType CrossingType, needAdjacent bool, visitor EdgePairVisitor) bool { |
| 29 | + minCrossingSign := MaybeCross |
| 30 | + if crossingType == CrossingTypeInterior { |
| 31 | + minCrossingSign = Cross |
| 32 | + } |
| 33 | + for i := 0; i < len(shapeEdges) - 1; i++ { |
| 34 | + a := shapeEdges[i] |
| 35 | + j := i + 1 |
| 36 | + // A common situation is that an edge AB is followed by an edge BC. We |
| 37 | + // only need to visit such crossings if "needAdjacent" is true (even if |
| 38 | + // AB and BC belong to different edge chains). |
| 39 | + if !needAdjacent && a.Edge.V1 == shapeEdges[j].Edge.V0 { |
| 40 | + j++ |
| 41 | + if j >= len(shapeEdges) { |
| 42 | + break |
| 43 | + } |
| 44 | + } |
| 45 | + crosser := NewEdgeCrosser(a.Edge.V0, a.Edge.V1) |
| 46 | + for ; j < len(shapeEdges); j++ { |
| 47 | + b := shapeEdges[j] |
| 48 | + if crosser.c != b.Edge.V0 { |
| 49 | + crosser.RestartAt(b.Edge.V0) |
| 50 | + } |
| 51 | + sign := crosser.ChainCrossingSign(b.Edge.V1) |
| 52 | + // missinglink: enum ordering is reversed compared to C++ |
| 53 | + if sign <= minCrossingSign { |
| 54 | + if !visitor(a, b, sign == Cross) { |
| 55 | + return false |
| 56 | + } |
| 57 | + } |
| 58 | + } |
| 59 | + } |
| 60 | + return true |
| 61 | +} |
| 62 | + |
| 63 | +// Visits all pairs of crossing edges in the given S2ShapeIndex, terminating |
| 64 | +// early if the given EdgePairVisitor function returns false (in which case |
| 65 | +// VisitCrossings returns false as well). "type" indicates whether all |
| 66 | +// crossings should be visited, or only interior crossings. |
| 67 | +// |
| 68 | +// If "needAdjacent" is false, then edge pairs of the form (AB, BC) may |
| 69 | +// optionally be ignored (even if the two edges belong to different edge |
| 70 | +// chains). This option exists for the benefit of FindSelfIntersection(), |
| 71 | +// which does not need such edge pairs (see below). |
| 72 | +func VisitCrossings(index *ShapeIndex, crossingType CrossingType, needAdjacent bool, visitor EdgePairVisitor) bool { |
| 73 | + // TODO(b/262264880): Use brute force if the total number of edges is small |
| 74 | + // enough (using a larger threshold if the S2ShapeIndex is not constructed |
| 75 | + // yet). |
| 76 | + for it := index.Iterator(); !it.Done(); it.Next() { |
| 77 | + shapeEdges := getShapeEdges(index, it.cell) |
| 78 | + if !visitCrossings(shapeEdges, crossingType, needAdjacent, visitor) { |
| 79 | + return false |
| 80 | + } |
| 81 | + } |
| 82 | + return true |
| 83 | +} |
| 84 | + |
| 85 | +// VisitCrossingEdgePairs finds all crossing edge pairs in an index. |
| 86 | +func VisitCrossingEdgePairs(index *ShapeIndex, crossingType CrossingType, visitor EdgePairVisitor) bool { |
| 87 | + needAdjacent := crossingType == CrossingTypeAll |
| 88 | + for it := index.Iterator(); !it.Done(); it.Next() { |
| 89 | + shapeEdges := getShapeEdges(index, it.cell) |
| 90 | + if !visitCrossings(shapeEdges, crossingType, needAdjacent, visitor) { |
| 91 | + return false |
| 92 | + } |
| 93 | + } |
| 94 | + return true |
| 95 | +} |
| 96 | + |
| 97 | +func FindCrossingError(shape Shape, a, b ShapeEdge, isInterior bool) error { |
| 98 | + ap := shape.ChainPosition(int(a.ID.EdgeID)) |
| 99 | + bp := shape.ChainPosition(int(b.ID.EdgeID)) |
| 100 | + |
| 101 | + if isInterior { |
| 102 | + if ap.ChainID != bp.ChainID { |
| 103 | + return fmt.Errorf( |
| 104 | + "Loop %d edge %d crosses loop %d edge %d", |
| 105 | + ap.ChainID, ap.Offset, bp.ChainID, bp.Offset) |
| 106 | + } |
| 107 | + return fmt.Errorf("Edge %d crosses edge %d", ap, bp) |
| 108 | + } |
| 109 | + |
| 110 | + // Loops are not allowed to have duplicate vertices, and separate loops |
| 111 | + // are not allowed to share edges or cross at vertices. We only need to |
| 112 | + // check a given vertex once, so we also require that the two edges have |
| 113 | + // the same end vertex |
| 114 | + if a.Edge.V1 != b.Edge.V1 { |
| 115 | + return nil |
| 116 | + } |
| 117 | + |
| 118 | + if ap.ChainID == bp.ChainID { |
| 119 | + return fmt.Errorf("Edge %d has duplicate vertex with edge %d", ap, bp) |
| 120 | + } |
| 121 | + |
| 122 | + aLen := shape.Chain(ap.ChainID).Length |
| 123 | + bLen := shape.Chain(bp.ChainID).Length |
| 124 | + aNext := ap.Offset + 1 |
| 125 | + if aNext == aLen { |
| 126 | + aNext = 0 |
| 127 | + } |
| 128 | + |
| 129 | + bNext := bp.Offset + 1 |
| 130 | + if bNext == bLen { |
| 131 | + bNext = 0 |
| 132 | + } |
| 133 | + |
| 134 | + a2 := shape.ChainEdge(ap.ChainID, aNext).V1 |
| 135 | + b2 := shape.ChainEdge(bp.ChainID, bNext).V1 |
| 136 | + |
| 137 | + if a.Edge.V0 == b.Edge.V0 || a.Edge.V0 == b2 { |
| 138 | + // The second edge index is sometimes off by one, hence "near". |
| 139 | + return fmt.Errorf( |
| 140 | + "Loop %d edge %d has duplicate near loop %d edge %d", |
| 141 | + ap.ChainID, ap.Offset, bp.ChainID, bp.Offset) |
| 142 | + } |
| 143 | + |
| 144 | + // Since S2ShapeIndex loops are oriented such that the polygon interior is |
| 145 | + // always on the left, we need to handle the case where one wedge contains |
| 146 | + // the complement of the other wedge. This is not specifically detected by |
| 147 | + // GetWedgeRelation, so there are two cases to check for. |
| 148 | + // |
| 149 | + // Note that we don't need to maintain any state regarding loop crossings |
| 150 | + // because duplicate edges are detected and rejected above. |
| 151 | + if WedgeRelation(a.Edge.V0, a.Edge.V1, a2, b.Edge.V0, b2) == WedgeProperlyOverlaps && |
| 152 | + WedgeRelation(a.Edge.V0, a.Edge.V1, a2, b2, b.Edge.V0) == WedgeProperlyOverlaps { |
| 153 | + return fmt.Errorf( |
| 154 | + "Loop %d edge %d crosses loop %d edge %d", |
| 155 | + ap.ChainID, ap.Offset, bp.ChainID, bp.Offset) |
| 156 | + } |
| 157 | + |
| 158 | + return nil |
| 159 | +} |
| 160 | + |
| 161 | +func FindSelfIntersection(index *ShapeIndex) bool { |
| 162 | + if len(index.shapes) == 0 { |
| 163 | + return false |
| 164 | + } |
| 165 | + shape := index.Shape(0) |
| 166 | + |
| 167 | + // Visit all crossing pairs except possibly for ones of the form (AB, BC), |
| 168 | + // since such pairs are very common and FindCrossingError() only needs pairs |
| 169 | + // of the form (AB, AC). |
| 170 | + return !VisitCrossings( |
| 171 | + index, CrossingTypeAll, false, |
| 172 | + func(a, b ShapeEdge, isInterior bool) bool { |
| 173 | + return FindCrossingError(shape, a, b, isInterior) == nil |
| 174 | + }, |
| 175 | + ) |
| 176 | +} |
0 commit comments