1 // Copyright 2005 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 // Original author: ericv@google.com (Eric Veach) 16 // Converted to D: madric@gmail.com (Vijay Nayar) 17 18 module s2.s2pointutil; 19 20 // Defines additional operations for points on the unit sphere (in addition to 21 // the standard vector operations defined in "util/math/vector.h"). 22 23 import math = std.math; 24 import s2.logger; 25 import s2.s1angle; 26 import s2.s2point; 27 import s2.util.math.matrix3x3; 28 import s2.util.math.vector; 29 30 // Uncomment the following line for testing purposes only. 31 // version = S2_TEST_DEGENERACIES; 32 33 // Return a unique "origin" on the sphere for operations that need a fixed 34 // reference point. In particular, this is the "point at infinity" used for 35 // point-in-polygon testing (by counting the number of edge crossings). 36 S2Point origin() { 37 version(S2_TEST_DEGENERACIES) { 38 // This value makes polygon operations much slower, because it greatly 39 // increases the number of degenerate cases that need to be handled using 40 // s2pred::ExpensiveSign(). 41 return S2Point(0, 0, 1); 42 } else { 43 // The origin should not be a point that is commonly used in edge tests in 44 // order to avoid triggering code to handle degenerate cases. (This rules 45 // out the north and south poles.) It should also not be on the boundary of 46 // any low-level S2Cell for the same reason. 47 // 48 // The point chosen here is about 66km from the north pole towards the East 49 // Siberian Sea. See the unittest for more details. It is written out 50 // explicitly using floating-point literals because the optimizer doesn't 51 // seem willing to evaluate Normalize() at compile time. 52 return S2Point(-0.0099994664350250197, 0.0025924542609324121, 0.99994664350250195); 53 } 54 } 55 56 // Return true if the given point is approximately unit length 57 // (this is mainly useful for assertions). 58 bool isUnitLength(in S2Point p) { 59 // Normalize() is guaranteed to return a vector whose L2-norm differs from 1 60 // by less than 2 * DBL_EPSILON. Thus the squared L2-norm differs by less 61 // than 4 * DBL_EPSILON. The actual calculated Norm2() can have up to 1.5 * 62 // DBL_EPSILON of additional error. The total error of 5.5 * DBL_EPSILON 63 // can then be rounded down since the result must be a representable 64 // double-precision value. 65 return math.fabs(p.norm2() - 1) <= 5 * double.epsilon; // About 1.11e-15 66 } 67 68 // Return true if two points are within the given distance of each other 69 // (this is mainly useful for testing). 70 bool approxEquals(in S2Point a, in S2Point b, S1Angle max_error = S1Angle.fromRadians(1e-15)) { 71 return S1Angle(a, b) <= max_error; 72 } 73 74 // Return a unit-length vector that is orthogonal to "a". Satisfies 75 // Ortho(-a) = -Ortho(a) for all a. 76 // 77 // Note that Vector3_d also defines an "Ortho" method, but this one is 78 // preferred for use in S2 code because it explicitly tries to avoid result 79 // result coordinates that are zero. (This is a performance optimization that 80 // reduces the amount of time spent in functions which handle degeneracies.) 81 S2Point ortho(in S2Point a) { 82 version (S2_TEST_DEGENERACIES) { 83 // Vector3.ortho() always returns a point on the X-Y, Y-Z, or X-Z planes. 84 // This leads to many more degenerate cases in polygon operations. 85 return a.ortho(); 86 } else { 87 int k = a.largestAbsComponent() - 1; 88 if (k < 0) { 89 k = 2; 90 } 91 S2Point temp = S2Point(0.012, 0.0053, 0.00457); 92 temp[k] = 1; 93 return a.crossProd(temp).normalize(); 94 } 95 } 96 97 // Return a vector "c" that is orthogonal to the given unit-length vectors 98 // "a" and "b". This function is similar to a.CrossProd(b) except that it 99 // does a better job of ensuring orthogonality when "a" is nearly parallel 100 // to "b", and it returns a non-zero result even when a == b or a == -b. 101 // 102 // It satisfies the following properties (RCP == RobustCrossProd): 103 // 104 // (1) RCP(a,b) != 0 for all a, b 105 // (2) RCP(b,a) == -RCP(a,b) unless a == b or a == -b 106 // (3) RCP(-a,b) == -RCP(a,b) unless a == b or a == -b 107 // (4) RCP(a,-b) == -RCP(a,b) unless a == b or a == -b 108 // 109 // The result is not guaranteed to be unit length. 110 S2Point robustCrossProd(in S2Point a, in S2Point b) { 111 if (!isUnitLength(a)) logger.logWarn("Invalid"); 112 if (!isUnitLength(b)) logger.logWarn("Invalid"); 113 // The direction of a.CrossProd(b) becomes unstable as (a + b) or (a - b) 114 // approaches zero. This leads to situations where a.CrossProd(b) is not 115 // very orthogonal to "a" and/or "b". We could fix this using Gram-Schmidt, 116 // but we also want b.RobustCrossProd(a) == -a.RobustCrossProd(b). 117 // 118 // The easiest fix is to just compute the cross product of (b+a) and (b-a). 119 // Mathematically, this cross product is exactly twice the cross product of 120 // "a" and "b", but it has the numerical advantage that (b+a) and (b-a) 121 // are always perpendicular (since "a" and "b" are unit length). This 122 // yields a result that is nearly orthogonal to both "a" and "b" even if 123 // these two values differ only in the lowest bit of one component. 124 Vector3_d x = (b + a).crossProd(b - a); 125 if (x != S2Point(0, 0, 0)) { 126 return x; 127 } 128 129 // The only result that makes sense mathematically is to return zero, but 130 // we find it more convenient to return an arbitrary orthogonal vector. 131 return ortho(a); 132 } 133 134 // Rotate the given point about the given axis by the given angle. "p" and 135 // "axis" must be unit length; "angle" has no restrictions (e.g., it can be 136 // positive, negative, greater than 360 degrees, etc). 137 S2Point rotate(in S2Point p, in S2Point axis, in S1Angle angle) 138 in { 139 assert(isUnitLength(p)); 140 assert(isUnitLength(axis)); 141 } do { 142 // Let M be the plane through P that is perpendicular to "axis", and let 143 // "center" be the point where M intersects "axis". We construct a 144 // right-handed orthogonal frame (dx, dy, center) such that "dx" is the 145 // vector from "center" to P, and "dy" has the same length as "dx". The 146 // result can then be expressed as (cos(angle)*dx + sin(angle)*dy + center). 147 S2Point center = p.dotProd(axis) * axis; 148 S2Point dx = p - center; 149 S2Point dy = axis.crossProd(p); 150 // Mathematically the result is unit length, but normalization is necessary 151 // to ensure that numerical errors don't accumulate. 152 return (angle.cos() * dx + angle.sin() * dy + center).normalize(); 153 } 154 155 // Extend the given point "z" on the unit sphere into a right-handed 156 // coordinate frame of unit-length column vectors m = (x,y,z). Note that the 157 // vectors (x,y) are an orthonormal frame for the tangent space at "z", while 158 // "z" itself is an orthonormal frame for the normal space at "z". 159 Matrix3x3_d getFrame(in S2Point z) { 160 Matrix3x3_d m; 161 getFrame(z, m); 162 return m; 163 } 164 165 void getFrame(in S2Point z, out Matrix3x3_d m) 166 in { 167 assert(isUnitLength(z)); 168 } do { 169 m.setCol(2, z); 170 m.setCol(1, ortho(z)); 171 m.setCol(0, m.col(1).crossProd(z)); // Already unit-length. 172 } 173 174 // Given an orthonormal basis "m" of column vectors and a point "p", return 175 // the coordinates of "p" with respect to the basis "m". The resulting 176 // point "q" satisfies the identity (m * q == p). 177 S2Point toFrame(in Matrix3x3_d m, in S2Point p) { 178 // The inverse of an orthonormal matrix is its transpose. 179 return m.transpose() * p; 180 } 181 182 // Given an orthonormal basis "m" of column vectors and a point "q" with 183 // respect to that basis, return the equivalent point "p" with respect to 184 // the standard axis-aligned basis. The result satisfies (p == m * q). 185 S2Point fromFrame(in Matrix3x3_d m, in S2Point q) { 186 return m * q; 187 } 188 189 // Return true if the points A, B, C are strictly counterclockwise. Return 190 // false if the points are clockwise or collinear (i.e. if they are all 191 // contained on some great circle). 192 // 193 // Due to numerical errors, situations may arise that are mathematically 194 // impossible, e.g. ABC may be considered strictly CCW while BCA is not. 195 // However, the implementation guarantees the following: 196 // 197 // If SimpleCCW(a,b,c), then !SimpleCCW(c,b,a) for all a,b,c. 198 deprecated("Use s2pred::Sign instead.") 199 bool simpleCCW(in S2Point a, in S2Point b, in S2Point c) { 200 // We compute the signed volume of the parallelepiped ABC. The usual 201 // formula for this is (AxB).C, but we compute it here using (CxA).B 202 // in order to ensure that ABC and CBA are not both CCW. This follows 203 // from the following identities (which are true numerically, not just 204 // mathematically): 205 // 206 // (1) x.CrossProd(y) == -(y.CrossProd(x)) 207 // (2) (-x).DotProd(y) == -(x.DotProd(y)) 208 209 return c.crossProd(a).dotProd(b) > 0; 210 }