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 16 // Original author: ericv@google.com (Eric Veach) 17 // Converted to D: madric@gmail.com (Vijay Nayar) 18 // 19 // Defines a collection of functions for: 20 // 21 // (1) Robustly clipping geodesic edges to the faces of the S2 biunit cube 22 // (see s2coords.h), and 23 // 24 // (2) Robustly clipping 2D edges against 2D rectangles. 25 // 26 // These functions can be used to efficiently find the set of S2CellIds that 27 // are intersected by a geodesic edge (e.g., see S2CrossingEdgeQuery). 28 29 module s2.s2edge_clipping; 30 31 import s2.r1interval; 32 import s2.r2point; 33 import s2.r2rect; 34 import s2.s2point; 35 36 import s2.s2pointutil : isUnitLength, robustCrossProd; 37 import s2.s2coords 38 : GetFace, GetUVWFace, FaceUVtoXYZ, FaceXYZtoUVW, ValidFaceXYZtoUV, XYZtoFaceUV; 39 40 import std.exception : enforce; 41 import std.algorithm : min, max; 42 import std.math : fabs, ldexp, signbit, SQRT1_2, SQRT2; 43 44 // FaceSegment represents an edge AB clipped to an S2 cube face. It is 45 // represented by a face index and a pair of (u,v) coordinates. 46 struct FaceSegment { 47 int face; 48 R2Point a, b; 49 } 50 51 alias FaceSegmentVector = FaceSegment[]; 52 53 // S2PointUVW is used to document that a given S2Point is expressed in the 54 // (u,v,w) coordinates of some cube face. 55 alias S2PointUVW = S2Point; 56 57 // Subdivides the given edge AB at every point where it crosses the boundary 58 // between two S2 cube faces and returns the corresponding FaceSegments. The 59 // segments are returned in order from A toward B. The input points must be 60 // unit length. 61 // 62 // This method guarantees that the returned segments form a continuous path 63 // from A to B, and that all vertices are within kFaceClipErrorUVDist of the 64 // line AB. All vertices lie within the [-1,1]x[-1,1] cube face rectangles. 65 // The results are consistent with s2pred::Sign(), i.e. the edge is 66 // well-defined even its endpoints are antipodal. [TODO(ericv): Extend the 67 // implementation of S2::RobustCrossProd so that this statement is true.] 68 void getFaceSegments(in S2Point a, in S2Point b, out FaceSegmentVector segments) 69 in { 70 assert(isUnitLength(a)); 71 assert(isUnitLength(b)); 72 } do { 73 // Fast path: both endpoints are on the same face. 74 FaceSegment segment; 75 int a_face = XYZtoFaceUV(a, segment.a); 76 int b_face = XYZtoFaceUV(b, segment.b); 77 if (a_face == b_face) { 78 segment.face = a_face; 79 segments ~= segment; 80 return; 81 } 82 83 // Starting at A, we follow AB from face to face until we reach the face 84 // containing B. The following code is designed to ensure that we always 85 // reach B, even in the presence of numerical errors. 86 // 87 // First we compute the normal to the plane containing A and B. This normal 88 // becomes the ultimate definition of the line AB; it is used to resolve all 89 // questions regarding where exactly the line goes. Unfortunately due to 90 // numerical errors, the line may not quite intersect the faces containing 91 // the original endpoints. We handle this by moving A and/or B slightly if 92 // necessary so that they are on faces intersected by the line AB. 93 S2Point ab = robustCrossProd(a, b); 94 a_face = moveOriginToValidFace(a_face, a, ab, segment.a); 95 b_face = moveOriginToValidFace(b_face, b, -ab, segment.b); 96 97 // Now we simply follow AB from face to face until we reach B. 98 segment.face = a_face; 99 R2Point b_saved = segment.b; 100 for (int face = a_face; face != b_face; ) { 101 // Complete the current segment by finding the point where AB exits the 102 // current face. 103 S2PointUVW n = FaceXYZtoUVW(face, ab); 104 int exit_axis = getExitAxis(n); 105 segment.b = getExitPoint(n, exit_axis); 106 segments ~= segment; 107 108 // Compute the next face intersected by AB, and translate the exit point 109 // of the current segment into the (u,v) coordinates of the next face. 110 // This becomes the first point of the next segment. 111 S2Point exit_xyz = FaceUVtoXYZ(face, segment.b); 112 face = getNextFace(face, segment.b, exit_axis, n, b_face); 113 S2PointUVW exit_uvw = FaceXYZtoUVW(face, exit_xyz); 114 segment.face = face; 115 segment.a = R2Point(exit_uvw[0], exit_uvw[1]); 116 } 117 // Finish the last segment. 118 segment.b = b_saved; 119 segments ~= segment; 120 } 121 122 // This helper function does two things. First, it clips the line segment AB 123 // to find the clipped destination B' on a given face. (The face is specified 124 // implicitly by expressing *all arguments* in the (u,v,w) coordinates of that 125 // face.) Second, it partially computes whether the segment AB intersects 126 // this face at all. The actual condition is fairly complicated, but it turns 127 // out that it can be expressed as a "score" that can be computed 128 // independently when clipping the two endpoints A and B. This function 129 // returns the score for the given endpoint, which is an integer ranging from 130 // 0 to 3. If the sum of the two scores is 3 or more, then AB does not 131 // intersect this face. See the calling function for the meaning of the 132 // various parameters. 133 private int clipDestination( 134 in S2PointUVW a, in S2PointUVW b, in S2PointUVW scaled_n, 135 in S2PointUVW a_tangent, in S2PointUVW b_tangent, double scale_uv, 136 out R2Point uv) 137 in { 138 assert(intersectsFace(scaled_n)); 139 } do { 140 141 // Optimization: if B is within the safe region of the face, use it. 142 const double kMaxSafeUVCoord = 1 - FACE_CLIP_ERROR_UV_COORD; 143 if (b[2] > 0) { 144 uv = R2Point(b[0] / b[2], b[1] / b[2]); 145 if (max(fabs(uv[0]), fabs(uv[1])) <= kMaxSafeUVCoord) 146 return 0; 147 } 148 // Otherwise find the point B' where the line AB exits the face. 149 uv = scale_uv * getExitPoint(scaled_n, getExitAxis(scaled_n)); 150 auto p = S2PointUVW(uv[0], uv[1], 1.0); 151 152 // Determine if the exit point B' is contained within the segment. We do this 153 // by computing the dot products with two inward-facing tangent vectors at A 154 // and B. If either dot product is negative, we say that B' is on the "wrong 155 // side" of that point. As the point B' moves around the great circle AB past 156 // the segment endpoint B, it is initially on the wrong side of B only; as it 157 // moves further it is on the wrong side of both endpoints; and then it is on 158 // the wrong side of A only. If the exit point B' is on the wrong side of 159 // either endpoint, we can't use it; instead the segment is clipped at the 160 // original endpoint B. 161 // 162 // We reject the segment if the sum of the scores of the two endpoints is 3 163 // or more. Here is what that rule encodes: 164 // - If B' is on the wrong side of A, then the other clipped endpoint A' 165 // must be in the interior of AB (otherwise AB' would go the wrong way 166 // around the circle). There is a similar rule for A'. 167 // - If B' is on the wrong side of either endpoint (and therefore we must 168 // use the original endpoint B instead), then it must be possible to 169 // project B onto this face (i.e., its w-coordinate must be positive). 170 // This rule is only necessary to handle certain zero-length edges (A=B). 171 int score = 0; 172 if ((p - a).dotProd(a_tangent) < 0) { 173 score = 2; // B' is on wrong side of A. 174 } else if ((p - b).dotProd(b_tangent) < 0) { 175 score = 1; // B' is on wrong side of B. 176 } 177 if (score > 0) { // B' is not in the interior of AB. 178 if (b[2] <= 0) { 179 score = 3; // B cannot be projected onto this face. 180 } else { 181 uv = R2Point(b[0] / b[2], b[1] / b[2]); 182 } 183 } 184 return score; 185 } 186 187 // Given an edge AB and a face, returns the (u,v) coordinates for the portion 188 // of AB that intersects that face. This method guarantees that the clipped 189 // vertices lie within the [-1,1]x[-1,1] cube face rectangle and are within 190 // kFaceClipErrorUVDist of the line AB, but the results may differ from 191 // those produced by GetFaceSegments. Returns false if AB does not 192 // intersect the given face. 193 bool clipToFace(in S2Point a, in S2Point b, int face, out R2Point a_uv, out R2Point b_uv) { 194 return clipToPaddedFace(a, b, face, 0.0, a_uv, b_uv); 195 } 196 197 // Like ClipToFace, but rather than clipping to the square [-1,1]x[-1,1] 198 // in (u,v) space, this method clips to [-R,R]x[-R,R] where R=(1+padding). 199 bool clipToPaddedFace( 200 in S2Point a_xyz, in S2Point b_xyz, int face, double padding, 201 out R2Point a_uv, out R2Point b_uv) 202 in { 203 assert(padding >= 0); 204 } do { 205 // Fast path: both endpoints are on the given face. 206 if (GetFace(a_xyz) == face && GetFace(b_xyz) == face) { 207 ValidFaceXYZtoUV(face, a_xyz, a_uv); 208 ValidFaceXYZtoUV(face, b_xyz, b_uv); 209 return true; 210 } 211 // Convert everything into the (u,v,w) coordinates of the given face. Note 212 // that the cross product *must* be computed in the original (x,y,z) 213 // coordinate system because RobustCrossProd (unlike the mathematical cross 214 // product) can produce different results in different coordinate systems 215 // when one argument is a linear multiple of the other, due to the use of 216 // symbolic perturbations. 217 S2PointUVW n = FaceXYZtoUVW(face, robustCrossProd(a_xyz, b_xyz)); 218 S2PointUVW a = FaceXYZtoUVW(face, a_xyz); 219 S2PointUVW b = FaceXYZtoUVW(face, b_xyz); 220 221 // Padding is handled by scaling the u- and v-components of the normal. 222 // Letting R=1+padding, this means that when we compute the dot product of 223 // the normal with a cube face vertex (such as (-1,-1,1)), we will actually 224 // compute the dot product with the scaled vertex (-R,-R,1). This allows 225 // methods such as IntersectsFace(), GetExitAxis(), etc, to handle padding 226 // with no further modifications. 227 const double scale_uv = 1 + padding; 228 auto scaled_n = S2PointUVW(scale_uv * n[0], scale_uv * n[1], n[2]); 229 if (!intersectsFace(scaled_n)) return false; 230 231 // TODO(ericv): This is a temporary hack until I rewrite S2::RobustCrossProd; 232 // it avoids loss of precision in Normalize() when the vector is so small 233 // that it underflows. 234 if (max(fabs(n[0]), max(fabs(n[1]), fabs(n[2]))) < ldexp(1.0, -511)) { 235 n *= ldexp(1.0, 563); 236 } // END OF HACK 237 n = n.normalize(); 238 S2PointUVW a_tangent = n.crossProd(a); 239 S2PointUVW b_tangent = b.crossProd(n); 240 // As described above, if the sum of the scores from clipping the two 241 // endpoints is 3 or more, then the segment does not intersect this face. 242 int a_score = clipDestination(b, a, -scaled_n, b_tangent, a_tangent, scale_uv, a_uv); 243 int b_score = clipDestination(a, b, scaled_n, a_tangent, b_tangent, scale_uv, b_uv); 244 return a_score + b_score < 3; 245 } 246 247 // The maximum error in the vertices returned by GetFaceSegments and 248 // ClipToFace (compared to an exact calculation): 249 // 250 // - kFaceClipErrorRadians is the maximum angle between a returned vertex 251 // and the nearest point on the exact edge AB. It is equal to the 252 // maximum directional error in S2::RobustCrossProd, plus the error when 253 // projecting points onto a cube face. 254 // 255 // - kFaceClipErrorDist is the same angle expressed as a maximum distance 256 // in (u,v)-space. In other words, a returned vertex is at most this far 257 // from the exact edge AB projected into (u,v)-space. 258 259 // - kFaceClipErrorUVCoord is the same angle expressed as the maximum error 260 // in an individual u- or v-coordinate. In other words, for each 261 // returned vertex there is a point on the exact edge AB whose u- and 262 // v-coordinates differ from the vertex by at most this amount. 263 264 enum double FACE_CLIP_ERROR_RADIANS = 3.0 * double.epsilon; 265 enum double FACE_CLIP_ERROR_UV_DIST = 9.0 * double.epsilon; 266 enum double FACE_CLIP_ERROR_UV_COORD = 9.0 * SQRT1_2 * double.epsilon; 267 268 // Returns true if the edge AB intersects the given (closed) rectangle to 269 // within the error bound below. 270 bool intersectsRect(in R2Point a, in R2Point b, in R2Rect rect) { 271 // First check whether the bound of AB intersects "rect". 272 R2Rect bound = R2Rect.fromPointPair(a, b); 273 if (!rect.intersects(bound)) return false; 274 275 // Otherwise AB intersects "rect" if and only if all four vertices of "rect" 276 // do not lie on the same side of the extended line AB. We test this by 277 // finding the two vertices of "rect" with minimum and maximum projections 278 // onto the normal of AB, and computing their dot products with the edge 279 // normal. 280 R2Point n = (b - a).ortho(); 281 int i = (n[0] >= 0) ? 1 : 0; 282 int j = (n[1] >= 0) ? 1 : 0; 283 double max = n.dotProd(rect.getVertex(i, j) - a); 284 double min = n.dotProd(rect.getVertex(1-i, 1-j) - a); 285 return (max >= 0) && (min <= 0); 286 } 287 288 private bool updateEndpoint(ref R1Interval bound, int end, double value) { 289 if (end == 0) { 290 if (bound.hi() < value) return false; 291 if (bound.lo() < value) bound.setLo(value); 292 } else { 293 if (bound.lo() > value) return false; 294 if (bound.hi() > value) bound.setHi(value); 295 } 296 return true; 297 } 298 299 // The maximum error in IntersectRect. If some point of AB is inside the 300 // rectangle by at least this distance, the result is guaranteed to be true; 301 // if all points of AB are outside the rectangle by at least this distance, 302 // the result is guaranteed to be false. This bound assumes that "rect" is 303 // a subset of the rectangle [-1,1]x[-1,1] or extends slightly outside it 304 // (e.g., by 1e-10 or less). 305 enum double INTERSECTS_RECT_ERROR_UV_DIST = 3 * SQRT2 * double.epsilon; 306 307 // Given an edge AB, returns the portion of AB that is contained by the given 308 // rectangle "clip". Returns false if there is no intersection. 309 bool clipEdge( 310 in R2Point a, in R2Point b, in R2Rect clip, out R2Point a_clipped, out R2Point b_clipped) { 311 // Compute the bounding rectangle of AB, clip it, and then extract the new 312 // endpoints from the clipped bound. 313 R2Rect bound = R2Rect.fromPointPair(a, b); 314 if (clipEdgeBound(a, b, clip, bound)) { 315 int ai = (a[0] > b[0]), aj = (a[1] > b[1]); 316 a_clipped = bound.getVertex(ai, aj); 317 b_clipped = bound.getVertex(1 - ai, 1 - aj); 318 return true; 319 } 320 return false; 321 } 322 323 // Given an edge AB and a rectangle "clip", returns the bounding rectangle of 324 // the portion of AB intersected by "clip". The resulting bound may be 325 // empty. This is a convenience function built on top of ClipEdgeBound. 326 R2Rect getClippedEdgeBound(in R2Point a, in R2Point b, in R2Rect clip) { 327 R2Rect bound = R2Rect.fromPointPair(a, b); 328 if (clipEdgeBound(a, b, clip, bound)) return bound; 329 return R2Rect.empty(); 330 } 331 332 // This function can be used to clip an edge AB to sequence of rectangles 333 // efficiently. It represents the clipped edges by their bounding boxes 334 // rather than as a pair of endpoints. Specifically, let A'B' be some 335 // portion of an edge AB, and let "bound" be a tight bound of A'B'. This 336 // function updates "bound" (in place) to be a tight bound of A'B' 337 // intersected with a given rectangle "clip". If A'B' does not intersect 338 // "clip", returns false and does not necessarily update "bound". 339 // 340 // REQUIRES: "bound" is a tight bounding rectangle for some portion of AB. 341 // (This condition is automatically satisfied if you start with the bounding 342 // box of AB and clip to a sequence of rectangles, stopping when the method 343 // returns false.) 344 bool clipEdgeBound(in R2Point a, in R2Point b, in R2Rect clip, ref R2Rect bound) { 345 // "diag" indicates which diagonal of the bounding box is spanned by AB: it 346 // is 0 if AB has positive slope, and 1 if AB has negative slope. This is 347 // used to determine which interval endpoints need to be updated each time 348 // the edge is clipped. 349 int diag = (a[0] > b[0]) != (a[1] > b[1]); 350 return clipBoundAxis(a[0], b[0], bound[0], a[1], b[1], bound[1], diag, clip[0]) 351 && clipBoundAxis(a[1], b[1], bound[1], a[0], b[0], bound[0], diag, clip[1]); 352 } 353 354 // Given a line segment from (a0,a1) to (b0,b1) and a bounding interval for 355 // each axis, clip the segment further if necessary so that "bound0" does not 356 // extend outside the given interval "clip". "diag" is a a precomputed helper 357 // variable that indicates which diagonal of the bounding box is spanned by AB: 358 // it is 0 if AB has positive slope, and 1 if AB has negative slope. 359 private bool clipBoundAxis( 360 double a0, double b0, ref R1Interval bound0, double a1, double b1, ref R1Interval bound1, 361 int diag, in R1Interval clip0) { 362 if (bound0.lo() < clip0.lo()) { 363 if (bound0.hi() < clip0.lo()) return false; 364 bound0[0] = clip0.lo(); 365 if (!updateEndpoint(bound1, diag, interpolateDouble(clip0.lo(), a0, b0, a1, b1))) 366 return false; 367 } 368 if (bound0.hi() > clip0.hi()) { 369 if (bound0.lo() > clip0.hi()) return false; 370 bound0[1] = clip0.hi(); 371 if (!updateEndpoint(bound1, 1 - diag, interpolateDouble(clip0.hi(), a0, b0, a1, b1))) 372 return false; 373 } 374 return true; 375 } 376 377 378 // The maximum error in the vertices generated by ClipEdge and the bounds 379 // generated by ClipEdgeBound (compared to an exact calculation): 380 // 381 // - kEdgeClipErrorUVCoord is the maximum error in a u- or v-coordinate 382 // compared to the exact result, assuming that the points A and B are in 383 // the rectangle [-1,1]x[1,1] or slightly outside it (by 1e-10 or less). 384 // 385 // - kEdgeClipErrorUVDist is the maximum distance from a clipped point to 386 // the corresponding exact result. It is equal to the error in a single 387 // coordinate because at most one coordinate is subject to error. 388 389 enum double EDGE_CLIP_ERROR_UV_COORD = 2.25 * double.epsilon; 390 enum double EDGE_CLIP_ERROR_UV_DIST = 2.25 * double.epsilon; 391 392 // Given a value x that is some linear combination of a and b, returns the 393 // value x1 that is the same linear combination of a1 and b1. This function 394 // makes the following guarantees: 395 // - If x == a, then x1 = a1 (exactly). 396 // - If x == b, then x1 = b1 (exactly). 397 // - If a <= x <= b, then a1 <= x1 <= b1 (even if a1 == b1). 398 // REQUIRES: a != b 399 double interpolateDouble(double x, double a, double b, double a1, double b1) 400 in { 401 assert(a != b); 402 } do { 403 // To get results that are accurate near both A and B, we interpolate 404 // starting from the closer of the two points. 405 if (fabs(a - x) <= fabs(b - x)) { 406 return a1 + (b1 - a1) * (x - a) / (b - a); 407 } else { 408 return b1 + (a1 - b1) * (x - b) / (a - b); 409 } 410 } 411 412 413 // Given a line segment AB whose origin A has been projected onto a given cube 414 // face, determine whether it is necessary to project A onto a different face 415 // instead. This can happen because the normal of the line AB is not computed 416 // exactly, so that the line AB (defined as the set of points perpendicular to 417 // the normal) may not intersect the cube face containing A. Even if it does 418 // intersect the face, the "exit point" of the line from that face may be on 419 // the wrong side of A (i.e., in the direction away from B). If this happens, 420 // we reproject A onto the adjacent face where the line AB approaches A most 421 // closely. This moves the origin by a small amount, but never more than the 422 // error tolerances documented in the header file. 423 private int moveOriginToValidFace(int face, in S2Point a, in S2Point ab, ref R2Point a_uv) { 424 // Fast path: if the origin is sufficiently far inside the face, it is 425 // always safe to use it. 426 const double kMaxSafeUVCoord = 1 - FACE_CLIP_ERROR_UV_COORD; 427 if (max(fabs(a_uv[0]), fabs(a_uv[1])) <= kMaxSafeUVCoord) { 428 return face; 429 } 430 // Otherwise check whether the normal AB even intersects this face. 431 S2PointUVW n = FaceXYZtoUVW(face, ab); 432 if (intersectsFace(n)) { 433 // Check whether the point where the line AB exits this face is on the 434 // wrong side of A (by more than the acceptable error tolerance). 435 S2Point exit = FaceUVtoXYZ(face, getExitPoint(n, getExitAxis(n))); 436 S2Point a_tangent = ab.normalize().crossProd(a); 437 if ((exit - a).dotProd(a_tangent) >= -FACE_CLIP_ERROR_RADIANS) { 438 return face; // We can use the given face. 439 } 440 } 441 // Otherwise we reproject A to the nearest adjacent face. (If line AB does 442 // not pass through a given face, it must pass through all adjacent faces.) 443 if (fabs(a_uv[0]) >= fabs(a_uv[1])) { 444 face = GetUVWFace(face, 0 /*U axis*/, a_uv[0] > 0); 445 } else { 446 face = GetUVWFace(face, 1 /*V axis*/, a_uv[1] > 0); 447 } 448 enforce(intersectsFace(FaceXYZtoUVW(face, ab))); 449 ValidFaceXYZtoUV(face, a, a_uv); 450 a_uv[0] = max(-1.0, min(1.0, a_uv[0])); 451 a_uv[1] = max(-1.0, min(1.0, a_uv[1])); 452 return face; 453 } 454 455 // Given cube face F and a directed line L (represented by its CCW normal N in 456 // the (u,v,w) coordinates of F), compute the axis of the cube face edge where 457 // L exits the face: return 0 if L exits through the u=-1 or u=+1 edge, and 1 458 // if L exits through the v=-1 or v=+1 edge. Either result is acceptable if L 459 // exits exactly through a corner vertex of the cube face. 460 private int getExitAxis(in S2PointUVW n) 461 in { 462 assert(intersectsFace(n)); 463 } do { 464 if (intersectsOppositeEdges(n)) { 465 // The line passes through through opposite edges of the face. 466 // It exits through the v=+1 or v=-1 edge if the u-component of N has a 467 // larger absolute magnitude than the v-component. 468 return (fabs(n[0]) >= fabs(n[1])) ? 1 : 0; 469 } else { 470 // The line passes through through two adjacent edges of the face. 471 // It exits the v=+1 or v=-1 edge if an even number of the components of N 472 // are negative. We test this using signbit() rather than multiplication 473 // to avoid the possibility of underflow. 474 enforce(n[0] != 0 && n[1] != 0 && n[2] != 0); 475 return ((signbit(n[0]) ^ signbit(n[1]) ^ signbit(n[2])) == 0) ? 1 : 0; 476 } 477 } 478 479 // Given a cube face F, a directed line L (represented by its CCW normal N in 480 // the (u,v,w) coordinates of F), and result of GetExitAxis(N), return the 481 // (u,v) coordinates of the point where L exits the cube face. 482 private R2Point getExitPoint(in S2PointUVW n, int axis) { 483 if (axis == 0) { 484 double u = (n[1] > 0) ? 1.0 : -1.0; 485 return R2Point(u, (-u * n[0] - n[2]) / n[1]); 486 } else { 487 double v = (n[0] < 0) ? 1.0 : -1.0; 488 return R2Point((-v * n[1] - n[2]) / n[0], v); 489 } 490 } 491 492 // Return the next face that should be visited by GetFaceSegments, given that 493 // we have just visited "face" and we are following the line AB (represented 494 // by its normal N in the (u,v,w) coordinates of that face). The other 495 // arguments include the point where AB exits "face", the corresponding 496 // exit axis, and the "target face" containing the destination point B. 497 private int getNextFace(int face, in R2Point exit, int axis, in S2PointUVW n, int target_face) { 498 // We return the face that is adjacent to the exit point along the given 499 // axis. If line AB exits *exactly* through a corner of the face, there are 500 // two possible next faces. If one is the "target face" containing B, then 501 // we guarantee that we advance to that face directly. 502 // 503 // The three conditions below check that (1) AB exits approximately through 504 // a corner, (2) the adjacent face along the non-exit axis is the target 505 // face, and (3) AB exits *exactly* through the corner. (The SumEquals() 506 // code checks whether the dot product of (u,v,1) and "n" is exactly zero.) 507 if (fabs(exit[1 - axis]) == 1 508 && GetUVWFace(face, 1 - axis, exit[1 - axis] > 0) == target_face 509 && sumEquals(exit[0] * n[0], exit[1] * n[1], -n[2])) { 510 return target_face; 511 } 512 // Otherwise return the face that is adjacent to the exit point in the 513 // direction of the exit axis. 514 return GetUVWFace(face, axis, exit[axis] > 0); 515 } 516 517 // The three functions below all compare a sum (u + v) to a third value w. 518 // They are implemented in such a way that they produce an exact result even 519 // though all calculations are done with ordinary floating-point operations. 520 // Here are the principles on which these functions are based: 521 // 522 // A. If u + v < w in floating-point, then u + v < w in exact arithmetic. 523 // 524 // B. If u + v < w in exact arithmetic, then at least one of the following 525 // expressions is true in floating-point: 526 // u + v < w 527 // u < w - v 528 // v < w - u 529 // 530 // Proof: By rearranging terms and substituting ">" for "<", we can assume 531 // that all values are non-negative. Now clearly "w" is not the smallest 532 // value, so assume WLOG that "u" is the smallest. We want to show that 533 // u < w - v in floating-point. If v >= w/2, the calculation of w - v is 534 // exact since the result is smaller in magnitude than either input value, 535 // so the result holds. Otherwise we have u <= v < w/2 and w - v >= w/2 536 // (even in floating point), so the result also holds. 537 538 // Return true if u + v == w exactly. 539 private bool sumEquals(double u, double v, double w) { 540 return (u + v == w) && (u == w - v) && (v == w - u); 541 } 542 543 // Return true if a given directed line L intersects the cube face F. The 544 // line L is defined by its normal N in the (u,v,w) coordinates of F. 545 private bool intersectsFace(in S2PointUVW n) { 546 // L intersects the [-1,1]x[-1,1] square in (u,v) if and only if the dot 547 // products of N with the four corner vertices (-1,-1,1), (1,-1,1), (1,1,1), 548 // and (-1,1,1) do not all have the same sign. This is true exactly when 549 // |Nu| + |Nv| >= |Nw|. The code below evaluates this expression exactly 550 // (see comments above). 551 double u = fabs(n[0]), v = fabs(n[1]), w = fabs(n[2]); 552 // We only need to consider the cases where u or v is the smallest value, 553 // since if w is the smallest then both expressions below will have a 554 // positive LHS and a negative RHS. 555 return (v >= w - u) && (u >= w - v); 556 } 557 558 // Given a directed line L intersecting a cube face F, return true if L 559 // intersects two opposite edges of F (including the case where L passes 560 // exactly through a corner vertex of F). The line L is defined by its 561 // normal N in the (u,v,w) coordinates of F. 562 private bool intersectsOppositeEdges(in S2PointUVW n) { 563 // The line L intersects opposite edges of the [-1,1]x[-1,1] (u,v) square if 564 // and only exactly two of the corner vertices lie on each side of L. This 565 // is true exactly when ||Nu| - |Nv|| >= |Nw|. The code below evaluates this 566 // expression exactly (see comments above). 567 double u = fabs(n[0]), v = fabs(n[1]), w = fabs(n[2]); 568 // If w is the smallest, the following line returns an exact result. 569 if (fabs(u - v) != w) return fabs(u - v) >= w; 570 // Otherwise u - v = w exactly, or w is not the smallest value. In either 571 // case the following line returns the correct result. 572 return (u >= v) ? (u - w >= v) : (v - w >= u); 573 }