14
14
15
15
package s2
16
16
17
+ import "slices"
18
+
17
19
// Shape interface enforcement
18
20
var _ Shape = (* LaxPolygon )(nil )
19
21
20
22
// LaxPolygon represents a region defined by a collection of zero or more
21
23
// closed loops. The interior is the region to the left of all loops. This
22
- // is similar to Polygon except that this class supports polygons
24
+ // is similar to Polygon except that this type supports polygons
23
25
// with degeneracies. Degeneracies are of two types: degenerate edges (from a
24
26
// vertex to itself) and sibling edge pairs (consisting of two oppositely
25
27
// oriented edges). Degeneracies can represent either "shells" or "holes"
@@ -28,50 +30,50 @@ var _ Shape = (*LaxPolygon)(nil)
28
30
// degenerate hole. Such edges form part of the boundary of the polygon.
29
31
//
30
32
// Loops with fewer than three vertices are interpreted as follows:
31
- // - A loop with two vertices defines two edges (in opposite directions).
32
- // - A loop with one vertex defines a single degenerate edge.
33
- // - A loop with no vertices is interpreted as the "full loop" containing
34
- //
35
- // all points on the sphere. If this loop is present, then all other loops
36
- // must form degeneracies (i.e., degenerate edges or sibling pairs). For
37
- // example, two loops {} and {X} would be interpreted as the full polygon
38
- // with a degenerate single-point hole at X.
33
+ // - A loop with two vertices defines two edges (in opposite directions).
34
+ // - A loop with one vertex defines a single degenerate edge.
35
+ // - A loop with no vertices is interpreted as the "full loop" containing
36
+ // all points on the sphere. If this loop is present, then all other loops
37
+ // must form degeneracies (i.e., degenerate edges or sibling pairs). For
38
+ // example, two loops {} and {X} would be interpreted as the full polygon
39
+ // with a degenerate single-point hole at X.
39
40
//
40
41
// LaxPolygon does not have any error checking, and it is perfectly fine to
41
42
// create LaxPolygon objects that do not meet the requirements below (e.g., in
42
43
// order to analyze or fix those problems). However, LaxPolygons must satisfy
43
44
// some additional conditions in order to perform certain operations:
44
45
//
45
- // - In order to be valid for point containment tests, the polygon must
46
- //
47
- // satisfy the "interior is on the left" rule. This means that there must
48
- // not be any crossing edges, and if there are duplicate edges then all but
49
- // at most one of them must belong to a sibling pair (i.e., the number of
50
- // edges in opposite directions must differ by at most one).
51
- //
52
- // - To be valid for polygon operations (BoundaryOperation), degenerate
46
+ // - In order to be valid for point containment tests, the polygon must
47
+ // satisfy the "interior is on the left" rule. This means that there must
48
+ // not be any crossing edges, and if there are duplicate edges then all but
49
+ // at most one of them must belong to a sibling pair (i.e., the number of
50
+ // edges in opposite directions must differ by at most one).
53
51
//
54
- // edges and sibling pairs cannot coincide with any other edges. For
55
- // example, the following situations are not allowed:
52
+ // - To be valid for polygon operations (BooleanOperation), degenerate
53
+ // edges and sibling pairs cannot coincide with any other edges. For
54
+ // example, the following situations are not allowed:
56
55
//
57
- // {AA, AA} // degenerate edge coincides with another edge
58
- // {AA, AB} // degenerate edge coincides with another edge
59
- // {AB, BA, AB} // sibling pair coincides with another edge
56
+ // {AA, AA} // degenerate edge coincides with another edge
57
+ // {AA, AB} // degenerate edge coincides with another edge
58
+ // {AB, BA, AB} // sibling pair coincides with another edge
60
59
//
61
60
// Note that LaxPolygon is much faster to initialize and is more compact than
62
61
// Polygon, but unlike Polygon it does not have any built-in operations.
63
- // Instead you should use ShapeIndex based operations such as BoundaryOperation ,
62
+ // Instead you should use ShapeIndex based operations such as BooleanOperation ,
64
63
// ClosestEdgeQuery, etc.
65
64
type LaxPolygon struct {
66
65
numLoops int
67
66
vertices []Point
68
67
69
- numVerts int
70
- cumulativeVertices []int
68
+ numVerts int
69
+ // when numLoops > 1, store a list of size (numLoops+1) where "i"
70
+ // represents the total number of vertices in loops 0..i-1.
71
+ loopStarts []int
72
+ }
71
73
72
- // TODO(roberts): C++ adds a prevLoop int field that claims to boost
73
- // chain position lookups by 1.5-4.5x. Benchmark to see if this
74
- // is useful here.
74
+ // LaxPolygonFromLoops creates a LaxPolygon from the given Polygon.
75
+ func LaxPolygonFromLoops ( l [] Loop ) * LaxPolygon {
76
+ return nil
75
77
}
76
78
77
79
// LaxPolygonFromPolygon creates a LaxPolygon from the given Polygon.
@@ -85,7 +87,17 @@ func LaxPolygonFromPolygon(p *Polygon) *LaxPolygon {
85
87
copy (spans [i ], loop .vertices )
86
88
}
87
89
}
88
- return LaxPolygonFromPoints (spans )
90
+ lax := LaxPolygonFromPoints (spans )
91
+
92
+ // Polygon and LaxPolygonShape holes are oriented oppositely, so we need
93
+ // to reverse the orientation of any loops representing holes.
94
+ for i := 0 ; i < p .NumLoops (); i ++ {
95
+ if p .Loop (i ).IsHole () {
96
+ v0 := lax .loopStarts [i ]
97
+ slices .Reverse (lax .vertices [v0 : v0 + lax .numLoopVertices (i )])
98
+ }
99
+ }
100
+ return lax
89
101
}
90
102
91
103
// LaxPolygonFromPoints creates a LaxPolygon from the given points.
@@ -99,19 +111,18 @@ func LaxPolygonFromPoints(loops [][]Point) *LaxPolygon {
99
111
case 1 :
100
112
p .numVerts = len (loops [0 ])
101
113
p .vertices = make ([]Point , p .numVerts )
114
+ p .loopStarts = []int {0 , 0 }
102
115
copy (p .vertices , loops [0 ])
103
116
default :
104
- p .cumulativeVertices = make ([] int , p . numLoops + 1 )
105
- numVertices := 0
117
+ p .numVerts = 0
118
+ p . loopStarts = make ([] int , p . numLoops + 1 )
106
119
for i , loop := range loops {
107
- p .cumulativeVertices [i ] = numVertices
108
- numVertices += len (loop )
120
+ p .loopStarts [i ] = p .numVerts
121
+ p .numVerts += len (loop )
122
+ p .vertices = append (p .vertices , loop ... )
109
123
}
110
124
111
- p .cumulativeVertices [p .numLoops ] = numVertices
112
- for _ , points := range loops {
113
- p .vertices = append (p .vertices , points ... )
114
- }
125
+ p .loopStarts [p .numLoops ] = p .numVerts
115
126
}
116
127
return p
117
128
}
@@ -121,58 +132,36 @@ func (p *LaxPolygon) numVertices() int {
121
132
if p .numLoops <= 1 {
122
133
return p .numVerts
123
134
}
124
- return p .cumulativeVertices [p .numLoops ]
135
+ return p .loopStarts [p .numLoops ]
125
136
}
126
137
127
138
// numLoopVertices reports the total number of vertices in the given loop.
128
139
func (p * LaxPolygon ) numLoopVertices (i int ) int {
129
140
if p .numLoops == 1 {
130
141
return p .numVerts
131
142
}
132
- return p .cumulativeVertices [i + 1 ] - p .cumulativeVertices [i ]
143
+ return p .loopStarts [i + 1 ] - p .loopStarts [i ]
133
144
}
134
145
135
146
// loopVertex returns the vertex from loop i at index j.
136
147
//
137
148
// This requires:
138
149
//
139
150
// 0 <= i < len(loops)
140
- // 0 <= j < len(loop[i].vertices )
151
+ // 0 <= j < numLoopVertices(i )
141
152
func (p * LaxPolygon ) loopVertex (i , j int ) Point {
142
153
if p .numLoops == 1 {
143
154
return p .vertices [j ]
144
155
}
145
156
146
- return p .vertices [p .cumulativeVertices [i ]+ j ]
157
+ return p .vertices [p .loopStarts [i ]+ j ]
147
158
}
148
159
149
160
func (p * LaxPolygon ) NumEdges () int { return p .numVertices () }
150
161
151
162
func (p * LaxPolygon ) Edge (e int ) Edge {
152
- e1 := e + 1
153
- if p .numLoops == 1 {
154
- // wrap the end vertex if this is the last edge.
155
- if e1 == p .numVerts {
156
- e1 = 0
157
- }
158
- return Edge {p .vertices [e ], p .vertices [e1 ]}
159
- }
160
-
161
- // TODO(roberts): If this turns out to be performance critical in tests
162
- // incorporate the maxLinearSearchLoops like in C++.
163
-
164
- // Check if e1 would cross a loop boundary in the set of all vertices.
165
- nextLoop := 0
166
- for p .cumulativeVertices [nextLoop ] <= e {
167
- nextLoop ++
168
- }
169
-
170
- // If so, wrap around to the first vertex of the loop.
171
- if e1 == p .cumulativeVertices [nextLoop ] {
172
- e1 = p .cumulativeVertices [nextLoop - 1 ]
173
- }
174
-
175
- return Edge {p .vertices [e ], p .vertices [e1 ]}
163
+ pos := p .ChainPosition (e )
164
+ return p .ChainEdge (pos .ChainID , pos .Offset )
176
165
}
177
166
178
167
func (p * LaxPolygon ) Dimension () int { return 2 }
@@ -184,10 +173,11 @@ func (p *LaxPolygon) ReferencePoint() ReferencePoint { return referencePointForS
184
173
func (p * LaxPolygon ) NumChains () int { return p .numLoops }
185
174
func (p * LaxPolygon ) Chain (i int ) Chain {
186
175
if p .numLoops == 1 {
187
- return Chain {0 , p .numVertices ()}
176
+ return Chain {Start : 0 , Length : p .numVertices ()}
188
177
}
189
- start := p .cumulativeVertices [i ]
190
- return Chain {start , p .cumulativeVertices [i + 1 ] - start }
178
+
179
+ start := p .loopStarts [i ]
180
+ return Chain {Start : start , Length : p .loopStarts [i + 1 ] - start }
191
181
}
192
182
193
183
func (p * LaxPolygon ) ChainEdge (i , j int ) Edge {
@@ -196,29 +186,33 @@ func (p *LaxPolygon) ChainEdge(i, j int) Edge {
196
186
if j + 1 != n {
197
187
k = j + 1
198
188
}
189
+
199
190
if p .numLoops == 1 {
200
- return Edge {p .vertices [j ], p .vertices [k ]}
191
+ return Edge {V0 : p .vertices [j ], V1 : p .vertices [k ]}
201
192
}
202
- base := p .cumulativeVertices [i ]
203
- return Edge {p .vertices [base + j ], p .vertices [base + k ]}
193
+
194
+ start := p .loopStarts [i ]
195
+ return Edge {V0 : p .vertices [start + j ], V1 : p .vertices [start + k ]}
204
196
}
205
197
206
- func (p * LaxPolygon ) ChainPosition (e int ) ChainPosition {
198
+ func (p * LaxPolygon ) ChainPosition (edgeID int ) ChainPosition {
199
+
207
200
if p .numLoops == 1 {
208
- return ChainPosition {0 , e }
201
+ return ChainPosition {ChainID : 0 , Offset : edgeID }
209
202
}
210
203
211
- // TODO(roberts): If this turns out to be performance critical in tests
212
- // incorporate the maxLinearSearchLoops like in C++.
213
-
214
- // Find the index of the first vertex of the loop following this one.
215
- nextLoop := 1
216
- for p .cumulativeVertices [nextLoop ] <= e {
204
+ // We need the loopStart that is less than or equal to the edgeID.
205
+ nextLoop := 0
206
+ for p .loopStarts [nextLoop ] <= edgeID {
217
207
nextLoop ++
218
208
}
219
209
220
- return ChainPosition {p .cumulativeVertices [nextLoop ] - p .cumulativeVertices [1 ], e - p .cumulativeVertices [nextLoop - 1 ]}
210
+ return ChainPosition {
211
+ ChainID : nextLoop - 1 ,
212
+ Offset : edgeID - p .loopStarts [nextLoop - 1 ],
213
+ }
221
214
}
222
215
223
216
// TODO(roberts): Remaining to port from C++:
224
- // EncodedLaxPolygon
217
+ // encode/decode
218
+ // Support for EncodedLaxPolygon
0 commit comments