public bool Contains(S2Cell cell) { // If the cap does not contain all cell vertices, return false. // We check the vertices before taking the Complement() because we can't // accurately represent the complement of a very small cap (a height // of 2-epsilon is rounded off to 2). var vertices = new S2Point[4]; for (var k = 0; k < 4; ++k) { vertices[k] = cell.GetVertex(k); if (!Contains(vertices[k])) { return(false); } } // Otherwise, return true if the complement of the cap does not intersect // the cell. (This test is slightly conservative, because technically we // want Complement().InteriorIntersects() here.) return(!Complement.Intersects(cell, vertices)); }
/** * Returns the smallest cell containing all four points, or * {@link S2CellId#sentinel()} if they are not all on the same face. The * points don't need to be normalized. */ private static S2CellId ContainingCell(S2Point pa, S2Point pb, S2Point pc, S2Point pd) { var a = S2CellId.FromPoint(pa); var b = S2CellId.FromPoint(pb); var c = S2CellId.FromPoint(pc); var d = S2CellId.FromPoint(pd); if (a.Face != b.Face || a.Face != c.Face || a.Face != d.Face) { return(S2CellId.Sentinel); } while (!a.Equals(b) || !a.Equals(c) || !a.Equals(d)) { a = a.Parent; b = b.Parent; c = c.Parent; d = d.Parent; } return(a); }
/** * Initializes the iterator to iterate over a set of candidates that may * cross the edge (a,b). */ public void GetCandidates(S2Point a, S2Point b) { _edgeIndex.PredictAdditionalCalls(1); _isBruteForce = !_edgeIndex.IsIndexComputed; if (_isBruteForce) { _edgeIndex.IncrementQueryCount(); _currentIndex = 0; _numEdges = _edgeIndex.NumEdges; } else { _candidates.Clear(); _edgeIndex.FindCandidateCrossings(a, b, _candidates); _currentIndexInCandidates = 0; if (_candidates.Any()) { _currentIndex = _candidates[0]; } } }
// S2Region interface (see {@code S2Region} for details): /** Return a bounding spherical cap. */ /** * The point 'p' does not need to be normalized. */ public bool Contains(S2Point p) { if (!_bound.Contains(p)) { return(false); } var inside = _originInside; var origin = S2.Origin; var crosser = new EdgeCrosser(origin, p, _vertices[_numVertices - 1]); // The s2edgeindex library is not optimized yet for long edges, // so the tradeoff to using it comes with larger loops. if (_numVertices < 2000) { for (var i = 0; i < _numVertices; i++) { inside ^= crosser.EdgeOrVertexCrossing(_vertices[i]); } } else { var it = GetEdgeIterator(_numVertices); it.GetCandidates(origin, p); var previousIndex = -2; foreach (var ai in it) // it.GetCandidates(origin, p); it.HasNext; it.Next()) { //var ai = it.Index; if (previousIndex != ai - 1) { crosser.RestartAt(_vertices[ai]); } previousIndex = ai; inside ^= crosser.EdgeOrVertexCrossing(Vertex(ai + 1)); } } return(inside); }
public static R2Vector ValidFaceXyzToUv(int face, S2Point p) { // assert (p.dotProd(faceUvToXyz(face, 0, 0)) > 0); double pu; double pv; switch (face) { case 0: pu = p.Y / p.X; pv = p.Z / p.X; break; case 1: pu = -p.X / p.Y; pv = p.Z / p.Y; break; case 2: pu = -p.X / p.Z; pv = -p.Y / p.Z; break; case 3: pu = p.Z / p.X; pv = p.Y / p.X; break; case 4: pu = p.Z / p.Y; pv = -p.X / p.Y; break; default: pu = -p.Y / p.Z; pv = -p.X / p.Z; break; } return(new R2Vector(pu, pv)); }
// S2Region interface (see S2Region.java for details): /** Return a bounding spherical cap. */ /** * The point 'p' does not need to be normalized. */ public bool Contains(S2Point p) { if (NumLoops == 1) { return(Loop(0).Contains(p)); // Optimization. } if (!_bound.Contains(p)) { return(false); } var inside = false; for (var i = 0; i < NumLoops; ++i) { inside ^= Loop(i).Contains(p); if (inside && !_hasHoles) { break; // Shells are disjoint. } } return(inside); }
/** * Return the approximate area of this cell. This method is accurate to within * 3% percent for all cell sizes and accurate to within 0.1% for cells at * level 5 or higher (i.e. 300km square or smaller). It is moderately cheap to * compute. */ public double ApproxArea() { // All cells at the first two levels have the same area. if (_level < 2) { return(AverageArea(_level)); } // First, compute the approximate area of the cell when projected // perpendicular to its normal. The cross product of its diagonals gives // the normal, and the length of the normal is twice the projected area. var flatArea = 0.5 * S2Point.CrossProd( GetVertex(2) - GetVertex(0), GetVertex(3) - GetVertex(1)).Norm; // Now, compensate for the curvature of the cell surface by pretending // that the cell is shaped like a spherical cap. The ratio of the // area of a spherical cap to the area of its projected disc turns out // to be 2 / (1 + sqrt(1 - r*r)) where "r" is the radius of the disc. // For example, when r=0 the ratio is 1, and when r=1 the ratio is 2. // Here we set Pi*r*r == flat_area to find the equivalent disc. return(flatArea * 2 / (1 + Math.Sqrt(1 - Math.Min(S2.InversePi * flatArea, 1.0)))); }
/** * Return true if the edges OA, OB, and OC are encountered in that order while * sweeping CCW around the point O. You can think of this as testing whether * A <= B <= C with respect to a continuous CCW ordering around O. * * Properties: * <ol> * <li>If orderedCCW(a,b,c,o) && orderedCCW(b,a,c,o), then a == b</li> * <li>If orderedCCW(a,b,c,o) && orderedCCW(a,c,b,o), then b == c</li> * <li>If orderedCCW(a,b,c,o) && orderedCCW(c,b,a,o), then a == b == c</li> * <li>If a == b or b == c, then orderedCCW(a,b,c,o) is true</li> * <li>Otherwise if a == c, then orderedCCW(a,b,c,o) is false</li> * </ol> */ public static bool OrderedCcw(S2Point a, S2Point b, S2Point c, S2Point o) { // The last inequality below is ">" rather than ">=" so that we return true // if A == B or B == C, and otherwise false if A == C. Recall that // RobustCCW(x,y,z) == -RobustCCW(z,y,x) for all x,y,z. var sum = 0; if (RobustCcw(b, o, a) >= 0) { ++sum; } if (RobustCcw(c, o, b) >= 0) { ++sum; } if (RobustCcw(a, o, c) > 0) { ++sum; } return(sum >= 2); }
/** * Given two edge chains (see WedgeRelation above), this function returns +1 * if A contains B, 0 if B contains A or the two wedges do not intersect, * and -1 if the edge chains A and B cross each other (i.e. if A intersects * both the interior and exterior of the region to the left of B). In * degenerate cases where more than one of these conditions is satisfied, * the maximum possible result is returned. For example, if A == B then the * result is +1. */ public int Test(S2Point a0, S2Point ab1, S2Point a2, S2Point b0, S2Point b2) { // There are 6 possible edge orderings at a shared vertex (all // of these orderings are circular, i.e. abcd == bcda): // // (1) a2 b2 b0 a0: A contains B // (2) a2 a0 b0 b2: B contains A // (3) a2 a0 b2 b0: A and B are disjoint // (4) a2 b0 a0 b2: A and B intersect in one wedge // (5) a2 b2 a0 b0: A and B intersect in one wedge // (6) a2 b0 b2 a0: A and B intersect in two wedges // // In cases (4-6), the boundaries of A and B cross (i.e. the boundary // of A intersects the interior and exterior of B and vice versa). // Thus we want to distinguish cases (1), (2-3), and (4-6). // // Note that the vertices may satisfy more than one of the edge // orderings above if two or more vertices are the same. The tests // below are written so that we take the most favorable // interpretation, i.e. preferring (1) over (2-3) over (4-6). In // particular note that if orderedCCW(a,b,c,o) returns true, it may be // possible that orderedCCW(c,b,a,o) is also true (if a == b or b == c). if (S2.OrderedCcw(a0, a2, b2, ab1)) { // The cases with this vertex ordering are 1, 5, and 6, // although case 2 is also possible if a2 == b2. if (S2.OrderedCcw(b2, b0, a0, ab1)) { return(1); // Case 1 (A contains B) } // We are in case 5 or 6, or case 2 if a2 == b2. return((a2.Equals(b2)) ? 0 : -1); // Case 2 vs. 5,6. } // We are in case 2, 3, or 4. return(S2.OrderedCcw(a0, b0, a2, ab1) ? 0 : -1); // Case 2,3 vs. 4. }
/** * If this method returns false, the region does not intersect the given cell. * Otherwise, either region intersects the cell, or the intersection * relationship could not be determined. */ public bool MayIntersect(S2Cell cell) { if (NumVertices == 0) { return(false); } // We only need to check whether the cell contains vertex 0 for correctness, // but these tests are cheap compared to edge crossings so we might as well // check all the vertices. for (var i = 0; i < NumVertices; ++i) { if (cell.Contains(Vertex(i))) { return(true); } } var cellVertices = new S2Point[4]; for (var i = 0; i < 4; ++i) { cellVertices[i] = cell.GetVertex(i); } for (var j = 0; j < 4; ++j) { var crosser = new EdgeCrosser(cellVertices[j], cellVertices[(j + 1) & 3], Vertex(0)); for (var i = 1; i < NumVertices; ++i) { if (crosser.RobustCrossing(Vertex(i)) >= 0) { // There is a proper crossing, or two vertices were the same. return(true); } } } return(false); }
/** * Returns the true centroid of the spherical triangle ABC multiplied by the * signed area of spherical triangle ABC. The reasons for multiplying by the * signed area are (1) this is the quantity that needs to be summed to compute * the centroid of a union or difference of triangles, and (2) it's actually * easier to calculate this way. */ public static S2Point TrueCentroid(S2Point a, S2Point b, S2Point c) { // I couldn't find any references for computing the true centroid of a // spherical triangle... I have a truly marvellous demonstration of this // formula which this margin is too narrow to contain :) // assert (isUnitLength(a) && isUnitLength(b) && isUnitLength(c)); var sina = S2Point.CrossProd(b, c).Norm; var sinb = S2Point.CrossProd(c, a).Norm; var sinc = S2Point.CrossProd(a, b).Norm; var ra = (sina == 0) ? 1 : (Math.Asin(sina) / sina); var rb = (sinb == 0) ? 1 : (Math.Asin(sinb) / sinb); var rc = (sinc == 0) ? 1 : (Math.Asin(sinc) / sinc); // Now compute a point M such that M.X = rX * det(ABC) / 2 for X in A,B,C. var x = new S2Point(a.X, b.X, c.X); var y = new S2Point(a.Y, b.Y, c.Y); var z = new S2Point(a.Z, b.Z, c.Z); var r = new S2Point(ra, rb, rc); return(new S2Point(0.5 * S2Point.CrossProd(y, z).DotProd(r), 0.5 * S2Point.CrossProd(z, x).DotProd(r), 0.5 * S2Point.CrossProd(x, y).DotProd(r))); }
/** * Return a vector "c" that is orthogonal to the given unit-length vectors "a" * and "b". This function is similar to a.CrossProd(b) except that it does a * better job of ensuring orthogonality when "a" is nearly parallel to "b", * and it returns a non-zero result even when a == b or a == -b. * * It satisfies the following properties (RCP == RobustCrossProd): * * (1) RCP(a,b) != 0 for all a, b (2) RCP(b,a) == -RCP(a,b) unless a == b or * a == -b (3) RCP(-a,b) == -RCP(a,b) unless a == b or a == -b (4) RCP(a,-b) * == -RCP(a,b) unless a == b or a == -b */ public static S2Point RobustCrossProd(S2Point a, S2Point b) { // The direction of a.CrossProd(b) becomes unstable as (a + b) or (a - b) // approaches zero. This leads to situations where a.CrossProd(b) is not // very orthogonal to "a" and/or "b". We could fix this using Gram-Schmidt, // but we also want b.RobustCrossProd(a) == -b.RobustCrossProd(a). // // The easiest fix is to just compute the cross product of (b+a) and (b-a). // Given that "a" and "b" are unit-length, this has good orthogonality to // "a" and "b" even if they differ only in the lowest bit of one component. // assert (isUnitLength(a) && isUnitLength(b)); var x = S2Point.CrossProd(b + a, b - a); if (!x.Equals(new S2Point(0, 0, 0))) { return(x); } // The only result that makes sense mathematically is to return zero, but // we find it more convenient to return an arbitrary orthogonal vector. return(Ortho(a)); }
private S2AreaCentroid GetAreaCentroid(bool doCentroid) { double areaSum = 0; var centroidSum = new S2Point(0, 0, 0); for (var i = 0; i < NumLoops; ++i) { var areaCentroid = doCentroid ? (S2AreaCentroid?)Loop(i).AreaAndCentroid : null; var loopArea = doCentroid ? areaCentroid.Value.Area : Loop(i).Area; var loopSign = Loop(i).Sign; areaSum += loopSign * loopArea; if (doCentroid) { var currentCentroid = areaCentroid.Value.Centroid.Value; centroidSum = new S2Point(centroidSum.X + loopSign * currentCentroid.X, centroidSum.Y + loopSign * currentCentroid.Y, centroidSum.Z + loopSign * currentCentroid.Z); } } return(new S2AreaCentroid(areaSum, doCentroid ? (S2Point?)centroidSum : null)); }
/** * Add the given edge to the polygon builder. This method should be used for * input data that may not follow S2 polygon conventions. Note that edges are * not allowed to cross each other. Also note that as a convenience, edges * where v0 == v1 are ignored. */ public void AddEdge(S2Point v0, S2Point v1) { // If xor_edges is true, we look for an existing edge in the opposite // direction. We either delete that edge or insert a new one. if (v0.Equals(v1)) { return; } if (_options.XorEdges) { HashBag <S2Point> candidates; _edges.TryGetValue(v1, out candidates); if (candidates != null && candidates.Any(c => c.Equals(v0))) { EraseEdge(v1, v0); return; } } if (!_edges.ContainsKey(v0)) { _edges[v0] = new HashBag <S2Point>(); } _edges[v0].Add(v1); if (_options.UndirectedEdges) { if (!_edges.ContainsKey(v1)) { _edges[v1] = new HashBag <S2Point>(); } _edges[v1].Add(v0); } }
/** * This method is equivalent to calling the S2EdgeUtil.robustCrossing() * function (defined below) on the edges AB and CD. It returns +1 if there * is a crossing, -1 if there is no crossing, and 0 if two points from * different edges are the same. Returns 0 or -1 if either edge is * degenerate. As a side effect, it saves vertex D to be used as the next * vertex C. */ public int RobustCrossing(S2Point d) { // For there to be an edge crossing, the triangles ACB, CBD, BDA, DAC must // all be oriented the same way (CW or CCW). We keep the orientation // of ACB as part of our state. When each new point D arrives, we // compute the orientation of BDA and check whether it matches ACB. // This checks whether the points C and D are on opposite sides of the // great circle through AB. // Recall that robustCCW is invariant with respect to rotating its // arguments, i.e. ABC has the same orientation as BDA. var bda = S2.RobustCcw(a, b, d, aCrossB); int result; if (bda == -acb && bda != 0) { // Most common case -- triangles have opposite orientations. result = -1; } else if ((bda & acb) == 0) { // At least one value is zero -- two vertices are identical. result = 0; } else { // assert (bda == acb && bda != 0); result = RobustCrossingInternal(d); // Slow path. } // Now save the current vertex D as the next vertex C, and also save the // orientation of the new triangle ACB (which is opposite to the current // triangle BDA). c = d; acb = -bda; return(result); }
/** * This class allows a vertex chain v0, v1, v2, ... to be efficiently tested * for intersection with a given fixed edge AB. */ /** * Return true if edge AB crosses CD at a point that is interior to both * edges. Properties: * * (1) simpleCrossing(b,a,c,d) == simpleCrossing(a,b,c,d) (2) * simpleCrossing(c,d,a,b) == simpleCrossing(a,b,c,d) */ public static bool SimpleCrossing(S2Point a, S2Point b, S2Point c, S2Point d) { // We compute simpleCCW() for triangles ACB, CBD, BDA, and DAC. All // of these triangles need to have the same orientation (CW or CCW) // for an intersection to exist. Note that this is slightly more // restrictive than the corresponding definition for planar edges, // since we need to exclude pairs of line segments that would // otherwise "intersect" by crossing two antipodal points. var ab = S2Point.CrossProd(a, b); var acb = -(ab.DotProd(c)); var bda = ab.DotProd(d); if (acb * bda <= 0) { return(false); } var cd = S2Point.CrossProd(c, d); var cbd = -(cd.DotProd(b)); var dac = cd.DotProd(a); return((acb * cbd > 0) && (acb * dac > 0)); }
// ////////////////////////////////////////////////////////////////////// // S2Region interface (see {@code S2Region} for details): /** * Return true if the cap intersects 'cell', given that the cap vertices have * alrady been checked. */ public bool Intersects(S2Cell cell, IReadOnlyList <S2Point> vertices) { // Return true if this cap intersects any point of 'cell' excluding its // vertices (which are assumed to already have been checked). // If the cap is a hemisphere or larger, the cell and the complement of the // cap are both convex. Therefore since no vertex of the cell is contained, // no other interior point of the cell is contained either. if (_height >= 1) { return(false); } // We need to check for empty caps due to the axis check just below. if (IsEmpty) { return(false); } // Optimization: return true if the cell contains the cap axis. (This // allows half of the edge checks below to be skipped.) if (cell.Contains(_axis)) { return(true); } // At this point we know that the cell does not contain the cap axis, // and the cap does not contain any cell vertex. The only way that they // can intersect is if the cap intersects the interior of some edge. var sin2Angle = _height * (2 - _height); // sin^2(capAngle) for (var k = 0; k < 4; ++k) { var edge = cell.GetEdgeRaw(k); var dot = _axis.DotProd(edge); if (dot > 0) { // The axis is in the interior half-space defined by the edge. We don't // need to consider these edges, since if the cap intersects this edge // then it also intersects the edge on the opposite side of the cell // (because we know the axis is not contained with the cell). continue; } // The Norm2() factor is necessary because "edge" is not normalized. if (dot * dot > sin2Angle * edge.Norm2) { return(false); // Entire cap is on the exterior side of this edge. } // Otherwise, the great circle containing this edge intersects // the interior of the cap. We just need to check whether the point // of closest approach occurs between the two edge endpoints. var dir = S2Point.CrossProd(edge, _axis); if (dir.DotProd(vertices[k]) < 0 && dir.DotProd(vertices[(k + 1) & 3]) > 0) { return(true); } } return(false); }
/** * Call this function when your chain 'jumps' to a new place. */ public void RestartAt(S2Point c) { this.c = c; acb = -S2.RobustCcw(a, b, c, aCrossB); }
/** *'interval' is the longitude interval to be tested against, and 'v0' is * the first vertex of edge chain. */ public LongitudePruner(S1Interval interval, S2Point v0) { this.interval = interval; lng0 = S2LatLng.Longitude(v0).Radians; }
/** * Create a cap given its axis and its area in steradians. 'axis' should be a * unit-length vector, and 'area' should be between 0 and 4 * M_PI. */ public static S2Cap FromAxisArea(S2Point axis, double area) { // assert (S2.isUnitLength(axis)); return(new S2Cap(axis, area / (2 * S2.Pi))); }
public CloserResult(double dmin2, S2Point vmin) { this.dmin2 = dmin2; this.vmin = vmin; }
/** * Return true if the edge AB intersects the given edge of constant latitude. */ private static bool IntersectsLatEdge(S2Point a, S2Point b, double lat, S1Interval lng) { // Return true if the segment AB intersects the given edge of constant // latitude. Unfortunately, lines of constant latitude are curves on // the sphere. They can intersect a straight edge in 0, 1, or 2 points. // assert (S2.isUnitLength(a) && S2.isUnitLength(b)); // First, compute the normal to the plane AB that points vaguely north. var z = S2Point.Normalize(S2.RobustCrossProd(a, b)); if (z.Z < 0) { z = -z; } // Extend this to an orthonormal frame (x,y,z) where x is the direction // where the great circle through AB achieves its maximium latitude. var y = S2Point.Normalize(S2.RobustCrossProd(z, new S2Point(0, 0, 1))); var x = S2Point.CrossProd(y, z); // assert (S2.isUnitLength(x) && x.z >= 0); // Compute the angle "theta" from the x-axis (in the x-y plane defined // above) where the great circle intersects the given line of latitude. var sinLat = Math.Sin(lat); if (Math.Abs(sinLat) >= x.Z) { return(false); // The great circle does not reach the given latitude. } // assert (x.z > 0); var cosTheta = sinLat / x.Z; var sinTheta = Math.Sqrt(1 - cosTheta * cosTheta); var theta = Math.Atan2(sinTheta, cosTheta); // The candidate intersection points are located +/- theta in the x-y // plane. For an intersection to be valid, we need to check that the // intersection point is contained in the interior of the edge AB and // also that it is contained within the given longitude interval "lng". // Compute the range of theta values spanned by the edge AB. var abTheta = S1Interval.FromPointPair(Math.Atan2( a.DotProd(y), a.DotProd(x)), Math.Atan2(b.DotProd(y), b.DotProd(x))); if (abTheta.Contains(theta)) { // Check if the intersection point is also in the given "lng" interval. var isect = (x * cosTheta) + (y * sinTheta); if (lng.Contains(Math.Atan2(isect.Y, isect.X))) { return(true); } } if (abTheta.Contains(-theta)) { // Check if the intersection point is also in the given "lng" interval. var intersection = (x * cosTheta) - (y * sinTheta); if (lng.Contains(Math.Atan2(intersection.Y, intersection.X))) { return(true); } } return(false); }
/** * Return the minimum distance from X to any point on the edge AB. The result * is very accurate for small distances but may have some numerical error if * the distance is large (approximately Pi/2 or greater). The case A == B is * handled correctly. Note: x, a and b must be of unit length. Throws * IllegalArgumentException if this is not the case. */ public static S1Angle GetDistance(S2Point x, S2Point a, S2Point b) { return(GetDistance(x, a, b, S2.RobustCrossProd(a, b))); }
public S2LatLngRect AddPoint(S2Point p) { return(AddPoint(new S2LatLng(p))); }
/** The point 'p' does not need to be normalized. */ public bool Contains(S2Point p) { return(Contains(new S2LatLng(p))); }
/** * Returns true if this rectangle intersects the given cell. (This is an exact * test and may be fairly expensive, see also MayIntersect below.) */ public bool Intersects(S2Cell cell) { // First we eliminate the cases where one region completely contains the // other. Once these are disposed of, then the regions will intersect // if and only if their boundaries intersect. if (IsEmpty) { return(false); } if (Contains(cell.Center)) { return(true); } if (cell.Contains(Center.ToPoint())) { return(true); } // Quick rejection test (not required for correctness). if (!Intersects(cell.RectBound)) { return(false); } // Now check whether the boundaries intersect. Unfortunately, a // latitude-longitude rectangle does not have straight edges -- two edges // are curved, and at least one of them is concave. // Precompute the cell vertices as points and latitude-longitudes. var cellV = new S2Point[4]; var cellLl = new S2LatLng[4]; for (var i = 0; i < 4; ++i) { cellV[i] = cell.GetVertex(i); // Must be normalized. cellLl[i] = new S2LatLng(cellV[i]); if (Contains(cellLl[i])) { return(true); // Quick acceptance test. } } for (var i = 0; i < 4; ++i) { var edgeLng = S1Interval.FromPointPair( cellLl[i].Lng.Radians, cellLl[(i + 1) & 3].Lng.Radians); if (!_lng.Intersects(edgeLng)) { continue; } var a = cellV[i]; var b = cellV[(i + 1) & 3]; if (edgeLng.Contains(_lng.Lo)) { if (IntersectsLngEdge(a, b, _lat, _lng.Lo)) { return(true); } } if (edgeLng.Contains(_lng.Hi)) { if (IntersectsLngEdge(a, b, _lat, _lng.Hi)) { return(true); } } if (IntersectsLatEdge(a, b, _lat.Lo, _lng)) { return(true); } if (IntersectsLatEdge(a, b, _lat.Hi, _lng)) { return(true); } } return(false); }
/** * Return true if and only if the given point is contained in the interior of * the region (i.e. the region excluding its boundary). The point 'p' does not * need to be normalized. */ public bool InteriorContains(S2Point p) { return(InteriorContains(new S2LatLng(p))); }
public bool Contains(S2Point p) { // The point 'p' should be a unit-length vector. // assert (S2.isUnitLength(p)); return((_axis - p).Norm2 <= 2 * _height); }
// Caps may be constructed from either an axis and a height, or an axis and // an angle. To avoid ambiguity, there are no public constructors //private S2Cap() //{ // _axis = new S2Point(); // _height = 0; //} private S2Cap(S2Point axis, double height) { _axis = axis; _height = height; // assert (isValid()); }
/** * Return true if and only if the given point is contained in the interior of * the region (i.e. the region excluding its boundary). 'p' should be a * unit-length vector. */ public bool InteriorContains(S2Point p) { // assert (S2.isUnitLength(p)); return(IsFull || (_axis - p).Norm2 < 2 * _height); }