/
shapeutil.go
228 lines (203 loc) · 8.13 KB
/
shapeutil.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s2
// CrossingType defines different ways of reporting edge intersections.
type CrossingType int
const (
// CrossingTypeInterior reports intersections that occur at a point
// interior to both edges (i.e., not at a vertex).
CrossingTypeInterior CrossingType = iota
// CrossingTypeAll reports all intersections, even those where two edges
// intersect only because they share a common vertex.
CrossingTypeAll
// CrossingTypeNonAdjacent reports all intersections except for pairs of
// the form (AB, BC) where both edges are from the same ShapeIndex.
CrossingTypeNonAdjacent
)
// rangeIterator is a wrapper over ShapeIndexIterator with extra methods
// that are useful for merging the contents of two or more ShapeIndexes.
type rangeIterator struct {
it *ShapeIndexIterator
// The min and max leaf cell ids covered by the current cell. If done() is
// true, these methods return a value larger than any valid cell id.
rangeMin CellID
rangeMax CellID
}
// newRangeIterator creates a new rangeIterator positioned at the first cell of the given index.
func newRangeIterator(index *ShapeIndex) *rangeIterator {
r := &rangeIterator{
it: index.Iterator(),
}
r.refresh()
return r
}
func (r *rangeIterator) cellID() CellID { return r.it.CellID() }
func (r *rangeIterator) indexCell() *ShapeIndexCell { return r.it.IndexCell() }
func (r *rangeIterator) next() { r.it.Next(); r.refresh() }
func (r *rangeIterator) done() bool { return r.it.Done() }
// seekTo positions the iterator at the first cell that overlaps or follows
// the current range minimum of the target iterator, i.e. such that its
// rangeMax >= target.rangeMin.
func (r *rangeIterator) seekTo(target *rangeIterator) {
r.it.seek(target.rangeMin)
// If the current cell does not overlap target, it is possible that the
// previous cell is the one we are looking for. This can only happen when
// the previous cell contains target but has a smaller CellID.
if r.it.Done() || r.it.CellID().RangeMin() > target.rangeMax {
if r.it.Prev() && r.it.CellID().RangeMax() < target.cellID() {
r.it.Next()
}
}
r.refresh()
}
// seekBeyond positions the iterator at the first cell that follows the current
// range minimum of the target iterator. i.e. the first cell such that its
// rangeMin > target.rangeMax.
func (r *rangeIterator) seekBeyond(target *rangeIterator) {
r.it.seek(target.rangeMax.Next())
if !r.it.Done() && r.it.CellID().RangeMin() <= target.rangeMax {
r.it.Next()
}
r.refresh()
}
// refresh updates the iterators min and max values.
func (r *rangeIterator) refresh() {
r.rangeMin = r.cellID().RangeMin()
r.rangeMax = r.cellID().RangeMax()
}
// referencePointForShape is a helper function for implementing various Shapes
// ReferencePoint functions.
//
// Given a shape consisting of closed polygonal loops, the interior of the
// shape is defined as the region to the left of all edges (which must be
// oriented consistently). This function then chooses an arbitrary point and
// returns true if that point is contained by the shape.
//
// Unlike Loop and Polygon, this method allows duplicate vertices and
// edges, which requires some extra care with definitions. The rule that we
// apply is that an edge and its reverse edge cancel each other: the result
// is the same as if that edge pair were not present. Therefore shapes that
// consist only of degenerate loop(s) are either empty or full; by convention,
// the shape is considered full if and only if it contains an empty loop (see
// laxPolygon for details).
//
// Determining whether a loop on the sphere contains a point is harder than
// the corresponding problem in 2D plane geometry. It cannot be implemented
// just by counting edge crossings because there is no such thing as a point
// at infinity that is guaranteed to be outside the loop.
//
// This function requires that the given Shape have an interior.
func referencePointForShape(shape Shape) ReferencePoint {
if shape.NumEdges() == 0 {
// A shape with no edges is defined to be full if and only if it
// contains at least one chain.
return OriginReferencePoint(shape.NumChains() > 0)
}
// Define a "matched" edge as one that can be paired with a corresponding
// reversed edge. Define a vertex as "balanced" if all of its edges are
// matched. In order to determine containment, we must find an unbalanced
// vertex. Often every vertex is unbalanced, so we start by trying an
// arbitrary vertex.
edge := shape.Edge(0)
if ref, ok := referencePointAtVertex(shape, edge.V0); ok {
return ref
}
// That didn't work, so now we do some extra work to find an unbalanced
// vertex (if any). Essentially we gather a list of edges and a list of
// reversed edges, and then sort them. The first edge that appears in one
// list but not the other is guaranteed to be unmatched.
n := shape.NumEdges()
var edges = make([]Edge, n)
var revEdges = make([]Edge, n)
for i := 0; i < n; i++ {
edge := shape.Edge(i)
edges[i] = edge
revEdges[i] = Edge{V0: edge.V1, V1: edge.V0}
}
sortEdges(edges)
sortEdges(revEdges)
for i := 0; i < n; i++ {
if edges[i].Cmp(revEdges[i]) == -1 { // edges[i] is unmatched
if ref, ok := referencePointAtVertex(shape, edges[i].V0); ok {
return ref
}
}
if revEdges[i].Cmp(edges[i]) == -1 { // revEdges[i] is unmatched
if ref, ok := referencePointAtVertex(shape, revEdges[i].V0); ok {
return ref
}
}
}
// All vertices are balanced, so this polygon is either empty or full except
// for degeneracies. By convention it is defined to be full if it contains
// any chain with no edges.
for i := 0; i < shape.NumChains(); i++ {
if shape.Chain(i).Length == 0 {
return OriginReferencePoint(true)
}
}
return OriginReferencePoint(false)
}
// referencePointAtVertex reports whether the given vertex is unbalanced, and
// returns a ReferencePoint indicating if the point is contained.
// Otherwise returns false.
func referencePointAtVertex(shape Shape, vTest Point) (ReferencePoint, bool) {
var ref ReferencePoint
// Let P be an unbalanced vertex. Vertex P is defined to be inside the
// region if the region contains a particular direction vector starting from
// P, namely the direction p.Ortho(). This can be calculated using
// ContainsVertexQuery.
containsQuery := NewContainsVertexQuery(vTest)
n := shape.NumEdges()
for e := 0; e < n; e++ {
edge := shape.Edge(e)
if edge.V0 == vTest {
containsQuery.AddEdge(edge.V1, 1)
}
if edge.V1 == vTest {
containsQuery.AddEdge(edge.V0, -1)
}
}
containsSign := containsQuery.ContainsVertex()
if containsSign == 0 {
return ref, false // There are no unmatched edges incident to this vertex.
}
ref.Point = vTest
ref.Contained = containsSign > 0
return ref, true
}
// containsBruteForce reports whether the given shape contains the given point.
// Most clients should not use this method, since its running time is linear in
// the number of shape edges. Instead clients should create a ShapeIndex and use
// ContainsPointQuery, since this strategy is much more efficient when many
// points need to be tested.
//
// Polygon boundaries are treated as being semi-open (see ContainsPointQuery
// and VertexModel for other options).
func containsBruteForce(shape Shape, point Point) bool {
if shape.Dimension() != 2 {
return false
}
refPoint := shape.ReferencePoint()
if refPoint.Point == point {
return refPoint.Contained
}
crosser := NewEdgeCrosser(refPoint.Point, point)
inside := refPoint.Contained
for e := 0; e < shape.NumEdges(); e++ {
edge := shape.Edge(e)
inside = inside != crosser.EdgeOrVertexCrossing(edge.V0, edge.V1)
}
return inside
}