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