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 }