1414
1515package s2
1616
17+ import "slices"
18+
1719// Shape interface enforcement
1820var _ Shape = (* LaxPolygon )(nil )
1921
2022// LaxPolygon represents a region defined by a collection of zero or more
2123// 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
2325// with degeneracies. Degeneracies are of two types: degenerate edges (from a
2426// vertex to itself) and sibling edge pairs (consisting of two oppositely
2527// oriented edges). Degeneracies can represent either "shells" or "holes"
@@ -28,50 +30,50 @@ var _ Shape = (*LaxPolygon)(nil)
2830// degenerate hole. Such edges form part of the boundary of the polygon.
2931//
3032// 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.
3940//
4041// LaxPolygon does not have any error checking, and it is perfectly fine to
4142// create LaxPolygon objects that do not meet the requirements below (e.g., in
4243// order to analyze or fix those problems). However, LaxPolygons must satisfy
4344// some additional conditions in order to perform certain operations:
4445//
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).
5351//
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:
5655//
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
6059//
6160// Note that LaxPolygon is much faster to initialize and is more compact than
6261// 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 ,
6463// ClosestEdgeQuery, etc.
6564type LaxPolygon struct {
6665 numLoops int
6766 vertices []Point
6867
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+ }
7173
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
7577}
7678
7779// LaxPolygonFromPolygon creates a LaxPolygon from the given Polygon.
@@ -85,7 +87,17 @@ func LaxPolygonFromPolygon(p *Polygon) *LaxPolygon {
8587 copy (spans [i ], loop .vertices )
8688 }
8789 }
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
89101}
90102
91103// LaxPolygonFromPoints creates a LaxPolygon from the given points.
@@ -99,19 +111,18 @@ func LaxPolygonFromPoints(loops [][]Point) *LaxPolygon {
99111 case 1 :
100112 p .numVerts = len (loops [0 ])
101113 p .vertices = make ([]Point , p .numVerts )
114+ p .loopStarts = []int {0 , 0 }
102115 copy (p .vertices , loops [0 ])
103116 default :
104- p .cumulativeVertices = make ([] int , p . numLoops + 1 )
105- numVertices := 0
117+ p .numVerts = 0
118+ p . loopStarts = make ([] int , p . numLoops + 1 )
106119 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 ... )
109123 }
110124
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
115126 }
116127 return p
117128}
@@ -121,58 +132,36 @@ func (p *LaxPolygon) numVertices() int {
121132 if p .numLoops <= 1 {
122133 return p .numVerts
123134 }
124- return p .cumulativeVertices [p .numLoops ]
135+ return p .loopStarts [p .numLoops ]
125136}
126137
127138// numLoopVertices reports the total number of vertices in the given loop.
128139func (p * LaxPolygon ) numLoopVertices (i int ) int {
129140 if p .numLoops == 1 {
130141 return p .numVerts
131142 }
132- return p .cumulativeVertices [i + 1 ] - p .cumulativeVertices [i ]
143+ return p .loopStarts [i + 1 ] - p .loopStarts [i ]
133144}
134145
135146// loopVertex returns the vertex from loop i at index j.
136147//
137148// This requires:
138149//
139150// 0 <= i < len(loops)
140- // 0 <= j < len(loop[i].vertices )
151+ // 0 <= j < numLoopVertices(i )
141152func (p * LaxPolygon ) loopVertex (i , j int ) Point {
142153 if p .numLoops == 1 {
143154 return p .vertices [j ]
144155 }
145156
146- return p .vertices [p .cumulativeVertices [i ]+ j ]
157+ return p .vertices [p .loopStarts [i ]+ j ]
147158}
148159
149160func (p * LaxPolygon ) NumEdges () int { return p .numVertices () }
150161
151162func (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 )
176165}
177166
178167func (p * LaxPolygon ) Dimension () int { return 2 }
@@ -184,10 +173,11 @@ func (p *LaxPolygon) ReferencePoint() ReferencePoint { return referencePointForS
184173func (p * LaxPolygon ) NumChains () int { return p .numLoops }
185174func (p * LaxPolygon ) Chain (i int ) Chain {
186175 if p .numLoops == 1 {
187- return Chain {0 , p .numVertices ()}
176+ return Chain {Start : 0 , Length : p .numVertices ()}
188177 }
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 }
191181}
192182
193183func (p * LaxPolygon ) ChainEdge (i , j int ) Edge {
@@ -196,29 +186,33 @@ func (p *LaxPolygon) ChainEdge(i, j int) Edge {
196186 if j + 1 != n {
197187 k = j + 1
198188 }
189+
199190 if p .numLoops == 1 {
200- return Edge {p .vertices [j ], p .vertices [k ]}
191+ return Edge {V0 : p .vertices [j ], V1 : p .vertices [k ]}
201192 }
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 ]}
204196}
205197
206- func (p * LaxPolygon ) ChainPosition (e int ) ChainPosition {
198+ func (p * LaxPolygon ) ChainPosition (edgeID int ) ChainPosition {
199+
207200 if p .numLoops == 1 {
208- return ChainPosition {0 , e }
201+ return ChainPosition {ChainID : 0 , Offset : edgeID }
209202 }
210203
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 {
217207 nextLoop ++
218208 }
219209
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+ }
221214}
222215
223216// TODO(roberts): Remaining to port from C++:
224- // EncodedLaxPolygon
217+ // encode/decode
218+ // Support for EncodedLaxPolygon
0 commit comments