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 
16 // Original Author: ericv@google.com (Eric Veach)
17 // Converted to D:  madric@gmail.com (Vijay Nayar)
18 
19 module s2.shapeutil.get_reference_point;
20 
21 import s2.s2contains_vertex_query;
22 import s2.s2point;
23 import s2.s2shape;
24 import s2.s2shape_index;
25 
26 import std.algorithm : sort;
27 import std.exception : enforce;
28 
29 // This is a helper function for implementing S2Shape::GetReferencePoint().
30 //
31 // Given a shape consisting of closed polygonal loops, the interior of the
32 // shape is defined as the region to the left of all edges (which must be
33 // oriented consistently).  This function then chooses an arbitrary point and
34 // returns true if that point is contained by the shape.
35 //
36 // Unlike S2Loop and S2Polygon, this method allows duplicate vertices and
37 // edges, which requires some extra care with definitions.  The rule that we
38 // apply is that an edge and its reverse edge "cancel" each other: the result
39 // is the same as if that edge pair were not present.  Therefore shapes that
40 // consist only of degenerate loop(s) are either empty or full; by convention,
41 // the shape is considered full if and only if it contains an empty loop (see
42 // S2LaxPolygonShape for details).
43 //
44 // Determining whether a loop on the sphere contains a point is harder than
45 // the corresponding problem in 2D plane geometry.  It cannot be implemented
46 // just by counting edge crossings because there is no such thing as a "point
47 // at infinity" that is guaranteed to be outside the loop.
48 S2Shape.ReferencePoint getReferencePoint(in S2Shape shape)
49 in {
50   assert(shape.hasInterior());
51 } do {
52   if (shape.numEdges() == 0) {
53     // A shape with no edges is defined to be "full" if and only if it
54     // contains an empty loop.
55     return S2Shape.ReferencePoint(shape.numChains() > 0);
56   }
57   // Define a "matched" edge as one that can be paired with a corresponding
58   // reversed edge.  Define a vertex as "balanced" if all of its edges are
59   // matched. In order to determine containment, we must find an unbalanced
60   // vertex.  Often every vertex is unbalanced, so we start by trying an
61   // arbitrary vertex.
62   auto edge = shape.edge(0);
63   S2Shape.ReferencePoint result;
64   if (getReferencePointAtVertex(shape, edge.v0, result)) {
65     return result;
66   }
67   // That didn't work, so now we do some extra work to find an unbalanced
68   // vertex (if any).  Essentially we gather a list of edges and a list of
69   // reversed edges, and then sort them.  The first edge that appears in one
70   // list but not the other is guaranteed to be unmatched.
71   int n = shape.numEdges();
72   auto edges = new S2Shape.Edge[n];
73   auto rev_edges = new S2Shape.Edge[n];
74   for (int i = 0; i < n; ++i) {
75     auto edge2 = shape.edge(i);
76     edges[i] = edge2;
77     rev_edges[i] = S2Shape.Edge(edge2.v1, edge2.v0);
78   }
79   sort(edges);
80   sort(rev_edges);
81   for (int i = 0; i < n; ++i) {
82     if (edges[i] < rev_edges[i]) {  // edges[i] is unmatched
83       enforce(getReferencePointAtVertex(shape, edges[i].v0, result));
84       return result;
85     }
86     if (rev_edges[i] < edges[i]) {  // rev_edges[i] is unmatched
87       enforce(getReferencePointAtVertex(shape, rev_edges[i].v0, result));
88       return result;
89     }
90   }
91   // All vertices are balanced, so this polygon is either empty or full.  By
92   // convention it is defined to be "full" if it contains any empty loop.
93   for (int i = 0; i < shape.numChains(); ++i) {
94     if (shape.chain(i).length == 0) return S2Shape.ReferencePoint(true);
95   }
96   return S2Shape.ReferencePoint(false);
97 }
98 
99 // This is a helper function for GetReferencePoint() below.
100 //
101 // If the given vertex "vtest" is unbalanced (see definition below), sets
102 // "result" to a ReferencePoint indicating whther "vtest" is contained and
103 // returns true.  Otherwise returns false.
104 private bool getReferencePointAtVertex(
105     in S2Shape shape, in S2Point vtest, ref S2Shape.ReferencePoint result) {
106   // Let P be an unbalanced vertex.  Vertex P is defined to be inside the
107   // region if the region contains a particular direction vector starting from
108   // P, namely the direction S2::Ortho(P).  This can be calculated using
109   // S2ContainsVertexQuery.
110   auto contains_query = new S2ContainsVertexQuery(vtest);
111   int n = shape.numEdges();
112   for (int e = 0; e < n; ++e) {
113     auto edge = shape.edge(e);
114     if (edge.v0 == vtest) contains_query.addEdge(edge.v1, 1);
115     if (edge.v1 == vtest) contains_query.addEdge(edge.v0, -1);
116   }
117   int contains_sign = contains_query.containsSign();
118   if (contains_sign == 0) {
119     return false;  // There are no unmatched edges incident to this vertex.
120   }
121   result.point = vtest;
122   result.contained = contains_sign > 0;
123   return true;
124 }
125