edge_clipping.go (26513B)
1 // Copyright 2017 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 package s2 16 17 // This file contains a collection of methods for: 18 // 19 // (1) Robustly clipping geodesic edges to the faces of the S2 biunit cube 20 // (see s2stuv), and 21 // 22 // (2) Robustly clipping 2D edges against 2D rectangles. 23 // 24 // These functions can be used to efficiently find the set of CellIDs that 25 // are intersected by a geodesic edge (e.g., see CrossingEdgeQuery). 26 27 import ( 28 "math" 29 30 "github.com/golang/geo/r1" 31 "github.com/golang/geo/r2" 32 "github.com/golang/geo/r3" 33 ) 34 35 const ( 36 // edgeClipErrorUVCoord is the maximum error in a u- or v-coordinate 37 // compared to the exact result, assuming that the points A and B are in 38 // the rectangle [-1,1]x[1,1] or slightly outside it (by 1e-10 or less). 39 edgeClipErrorUVCoord = 2.25 * dblEpsilon 40 41 // edgeClipErrorUVDist is the maximum distance from a clipped point to 42 // the corresponding exact result. It is equal to the error in a single 43 // coordinate because at most one coordinate is subject to error. 44 edgeClipErrorUVDist = 2.25 * dblEpsilon 45 46 // faceClipErrorRadians is the maximum angle between a returned vertex 47 // and the nearest point on the exact edge AB. It is equal to the 48 // maximum directional error in PointCross, plus the error when 49 // projecting points onto a cube face. 50 faceClipErrorRadians = 3 * dblEpsilon 51 52 // faceClipErrorDist is the same angle expressed as a maximum distance 53 // in (u,v)-space. In other words, a returned vertex is at most this far 54 // from the exact edge AB projected into (u,v)-space. 55 faceClipErrorUVDist = 9 * dblEpsilon 56 57 // faceClipErrorUVCoord is the maximum angle between a returned vertex 58 // and the nearest point on the exact edge AB expressed as the maximum error 59 // in an individual u- or v-coordinate. In other words, for each 60 // returned vertex there is a point on the exact edge AB whose u- and 61 // v-coordinates differ from the vertex by at most this amount. 62 faceClipErrorUVCoord = 9.0 * (1.0 / math.Sqrt2) * dblEpsilon 63 64 // intersectsRectErrorUVDist is the maximum error when computing if a point 65 // intersects with a given Rect. If some point of AB is inside the 66 // rectangle by at least this distance, the result is guaranteed to be true; 67 // if all points of AB are outside the rectangle by at least this distance, 68 // the result is guaranteed to be false. This bound assumes that rect is 69 // a subset of the rectangle [-1,1]x[-1,1] or extends slightly outside it 70 // (e.g., by 1e-10 or less). 71 intersectsRectErrorUVDist = 3 * math.Sqrt2 * dblEpsilon 72 ) 73 74 // ClipToFace returns the (u,v) coordinates for the portion of the edge AB that 75 // intersects the given face, or false if the edge AB does not intersect. 76 // This method guarantees that the clipped vertices lie within the [-1,1]x[-1,1] 77 // cube face rectangle and are within faceClipErrorUVDist of the line AB, but 78 // the results may differ from those produced by FaceSegments. 79 func ClipToFace(a, b Point, face int) (aUV, bUV r2.Point, intersects bool) { 80 return ClipToPaddedFace(a, b, face, 0.0) 81 } 82 83 // ClipToPaddedFace returns the (u,v) coordinates for the portion of the edge AB that 84 // intersects the given face, but rather than clipping to the square [-1,1]x[-1,1] 85 // in (u,v) space, this method clips to [-R,R]x[-R,R] where R=(1+padding). 86 // Padding must be non-negative. 87 func ClipToPaddedFace(a, b Point, f int, padding float64) (aUV, bUV r2.Point, intersects bool) { 88 // Fast path: both endpoints are on the given face. 89 if face(a.Vector) == f && face(b.Vector) == f { 90 au, av := validFaceXYZToUV(f, a.Vector) 91 bu, bv := validFaceXYZToUV(f, b.Vector) 92 return r2.Point{au, av}, r2.Point{bu, bv}, true 93 } 94 95 // Convert everything into the (u,v,w) coordinates of the given face. Note 96 // that the cross product *must* be computed in the original (x,y,z) 97 // coordinate system because PointCross (unlike the mathematical cross 98 // product) can produce different results in different coordinate systems 99 // when one argument is a linear multiple of the other, due to the use of 100 // symbolic perturbations. 101 normUVW := pointUVW(faceXYZtoUVW(f, a.PointCross(b))) 102 aUVW := pointUVW(faceXYZtoUVW(f, a)) 103 bUVW := pointUVW(faceXYZtoUVW(f, b)) 104 105 // Padding is handled by scaling the u- and v-components of the normal. 106 // Letting R=1+padding, this means that when we compute the dot product of 107 // the normal with a cube face vertex (such as (-1,-1,1)), we will actually 108 // compute the dot product with the scaled vertex (-R,-R,1). This allows 109 // methods such as intersectsFace, exitAxis, etc, to handle padding 110 // with no further modifications. 111 scaleUV := 1 + padding 112 scaledN := pointUVW{r3.Vector{X: scaleUV * normUVW.X, Y: scaleUV * normUVW.Y, Z: normUVW.Z}} 113 if !scaledN.intersectsFace() { 114 return aUV, bUV, false 115 } 116 117 // TODO(roberts): This is a workaround for extremely small vectors where some 118 // loss of precision can occur in Normalize causing underflow. When PointCross 119 // is updated to work around this, this can be removed. 120 if math.Max(math.Abs(normUVW.X), math.Max(math.Abs(normUVW.Y), math.Abs(normUVW.Z))) < math.Ldexp(1, -511) { 121 normUVW = pointUVW{normUVW.Mul(math.Ldexp(1, 563))} 122 } 123 124 normUVW = pointUVW{normUVW.Normalize()} 125 126 aTan := pointUVW{normUVW.Cross(aUVW.Vector)} 127 bTan := pointUVW{bUVW.Cross(normUVW.Vector)} 128 129 // As described in clipDestination, if the sum of the scores from clipping the two 130 // endpoints is 3 or more, then the segment does not intersect this face. 131 aUV, aScore := clipDestination(bUVW, aUVW, pointUVW{scaledN.Mul(-1)}, bTan, aTan, scaleUV) 132 bUV, bScore := clipDestination(aUVW, bUVW, scaledN, aTan, bTan, scaleUV) 133 134 return aUV, bUV, aScore+bScore < 3 135 } 136 137 // ClipEdge returns the portion of the edge defined by AB that is contained by the 138 // given rectangle. If there is no intersection, false is returned and aClip and bClip 139 // are undefined. 140 func ClipEdge(a, b r2.Point, clip r2.Rect) (aClip, bClip r2.Point, intersects bool) { 141 // Compute the bounding rectangle of AB, clip it, and then extract the new 142 // endpoints from the clipped bound. 143 bound := r2.RectFromPoints(a, b) 144 if bound, intersects = clipEdgeBound(a, b, clip, bound); !intersects { 145 return aClip, bClip, false 146 } 147 ai := 0 148 if a.X > b.X { 149 ai = 1 150 } 151 aj := 0 152 if a.Y > b.Y { 153 aj = 1 154 } 155 156 return bound.VertexIJ(ai, aj), bound.VertexIJ(1-ai, 1-aj), true 157 } 158 159 // The three functions below (sumEqual, intersectsFace, intersectsOppositeEdges) 160 // all compare a sum (u + v) to a third value w. They are implemented in such a 161 // way that they produce an exact result even though all calculations are done 162 // with ordinary floating-point operations. Here are the principles on which these 163 // functions are based: 164 // 165 // A. If u + v < w in floating-point, then u + v < w in exact arithmetic. 166 // 167 // B. If u + v < w in exact arithmetic, then at least one of the following 168 // expressions is true in floating-point: 169 // u + v < w 170 // u < w - v 171 // v < w - u 172 // 173 // Proof: By rearranging terms and substituting ">" for "<", we can assume 174 // that all values are non-negative. Now clearly "w" is not the smallest 175 // value, so assume WLOG that "u" is the smallest. We want to show that 176 // u < w - v in floating-point. If v >= w/2, the calculation of w - v is 177 // exact since the result is smaller in magnitude than either input value, 178 // so the result holds. Otherwise we have u <= v < w/2 and w - v >= w/2 179 // (even in floating point), so the result also holds. 180 181 // sumEqual reports whether u + v == w exactly. 182 func sumEqual(u, v, w float64) bool { 183 return (u+v == w) && (u == w-v) && (v == w-u) 184 } 185 186 // pointUVW represents a Point in (u,v,w) coordinate space of a cube face. 187 type pointUVW Point 188 189 // intersectsFace reports whether a given directed line L intersects the cube face F. 190 // The line L is defined by its normal N in the (u,v,w) coordinates of F. 191 func (p pointUVW) intersectsFace() bool { 192 // L intersects the [-1,1]x[-1,1] square in (u,v) if and only if the dot 193 // products of N with the four corner vertices (-1,-1,1), (1,-1,1), (1,1,1), 194 // and (-1,1,1) do not all have the same sign. This is true exactly when 195 // |Nu| + |Nv| >= |Nw|. The code below evaluates this expression exactly. 196 u := math.Abs(p.X) 197 v := math.Abs(p.Y) 198 w := math.Abs(p.Z) 199 200 // We only need to consider the cases where u or v is the smallest value, 201 // since if w is the smallest then both expressions below will have a 202 // positive LHS and a negative RHS. 203 return (v >= w-u) && (u >= w-v) 204 } 205 206 // intersectsOppositeEdges reports whether a directed line L intersects two 207 // opposite edges of a cube face F. This includs the case where L passes 208 // exactly through a corner vertex of F. The directed line L is defined 209 // by its normal N in the (u,v,w) coordinates of F. 210 func (p pointUVW) intersectsOppositeEdges() bool { 211 // The line L intersects opposite edges of the [-1,1]x[-1,1] (u,v) square if 212 // and only exactly two of the corner vertices lie on each side of L. This 213 // is true exactly when ||Nu| - |Nv|| >= |Nw|. The code below evaluates this 214 // expression exactly. 215 u := math.Abs(p.X) 216 v := math.Abs(p.Y) 217 w := math.Abs(p.Z) 218 219 // If w is the smallest, the following line returns an exact result. 220 if math.Abs(u-v) != w { 221 return math.Abs(u-v) >= w 222 } 223 224 // Otherwise u - v = w exactly, or w is not the smallest value. In either 225 // case the following returns the correct result. 226 if u >= v { 227 return u-w >= v 228 } 229 return v-w >= u 230 } 231 232 // axis represents the possible results of exitAxis. 233 type axis int 234 235 const ( 236 axisU axis = iota 237 axisV 238 ) 239 240 // exitAxis reports which axis the directed line L exits the cube face F on. 241 // The directed line L is represented by its CCW normal N in the (u,v,w) coordinates 242 // of F. It returns axisU if L exits through the u=-1 or u=+1 edge, and axisV if L exits 243 // through the v=-1 or v=+1 edge. Either result is acceptable if L exits exactly 244 // through a corner vertex of the cube face. 245 func (p pointUVW) exitAxis() axis { 246 if p.intersectsOppositeEdges() { 247 // The line passes through through opposite edges of the face. 248 // It exits through the v=+1 or v=-1 edge if the u-component of N has a 249 // larger absolute magnitude than the v-component. 250 if math.Abs(p.X) >= math.Abs(p.Y) { 251 return axisV 252 } 253 return axisU 254 } 255 256 // The line passes through through two adjacent edges of the face. 257 // It exits the v=+1 or v=-1 edge if an even number of the components of N 258 // are negative. We test this using signbit() rather than multiplication 259 // to avoid the possibility of underflow. 260 var x, y, z int 261 if math.Signbit(p.X) { 262 x = 1 263 } 264 if math.Signbit(p.Y) { 265 y = 1 266 } 267 if math.Signbit(p.Z) { 268 z = 1 269 } 270 271 if x^y^z == 0 { 272 return axisV 273 } 274 return axisU 275 } 276 277 // exitPoint returns the UV coordinates of the point where a directed line L (represented 278 // by the CCW normal of this point), exits the cube face this point is derived from along 279 // the given axis. 280 func (p pointUVW) exitPoint(a axis) r2.Point { 281 if a == axisU { 282 u := -1.0 283 if p.Y > 0 { 284 u = 1.0 285 } 286 return r2.Point{u, (-u*p.X - p.Z) / p.Y} 287 } 288 289 v := -1.0 290 if p.X < 0 { 291 v = 1.0 292 } 293 return r2.Point{(-v*p.Y - p.Z) / p.X, v} 294 } 295 296 // clipDestination returns a score which is used to indicate if the clipped edge AB 297 // on the given face intersects the face at all. This function returns the score for 298 // the given endpoint, which is an integer ranging from 0 to 3. If the sum of the scores 299 // from both of the endpoints is 3 or more, then edge AB does not intersect this face. 300 // 301 // First, it clips the line segment AB to find the clipped destination B' on a given 302 // face. (The face is specified implicitly by expressing *all arguments* in the (u,v,w) 303 // coordinates of that face.) Second, it partially computes whether the segment AB 304 // intersects this face at all. The actual condition is fairly complicated, but it 305 // turns out that it can be expressed as a "score" that can be computed independently 306 // when clipping the two endpoints A and B. 307 func clipDestination(a, b, scaledN, aTan, bTan pointUVW, scaleUV float64) (r2.Point, int) { 308 var uv r2.Point 309 310 // Optimization: if B is within the safe region of the face, use it. 311 maxSafeUVCoord := 1 - faceClipErrorUVCoord 312 if b.Z > 0 { 313 uv = r2.Point{b.X / b.Z, b.Y / b.Z} 314 if math.Max(math.Abs(uv.X), math.Abs(uv.Y)) <= maxSafeUVCoord { 315 return uv, 0 316 } 317 } 318 319 // Otherwise find the point B' where the line AB exits the face. 320 uv = scaledN.exitPoint(scaledN.exitAxis()).Mul(scaleUV) 321 322 p := pointUVW(Point{r3.Vector{uv.X, uv.Y, 1.0}}) 323 324 // Determine if the exit point B' is contained within the segment. We do this 325 // by computing the dot products with two inward-facing tangent vectors at A 326 // and B. If either dot product is negative, we say that B' is on the "wrong 327 // side" of that point. As the point B' moves around the great circle AB past 328 // the segment endpoint B, it is initially on the wrong side of B only; as it 329 // moves further it is on the wrong side of both endpoints; and then it is on 330 // the wrong side of A only. If the exit point B' is on the wrong side of 331 // either endpoint, we can't use it; instead the segment is clipped at the 332 // original endpoint B. 333 // 334 // We reject the segment if the sum of the scores of the two endpoints is 3 335 // or more. Here is what that rule encodes: 336 // - If B' is on the wrong side of A, then the other clipped endpoint A' 337 // must be in the interior of AB (otherwise AB' would go the wrong way 338 // around the circle). There is a similar rule for A'. 339 // - If B' is on the wrong side of either endpoint (and therefore we must 340 // use the original endpoint B instead), then it must be possible to 341 // project B onto this face (i.e., its w-coordinate must be positive). 342 // This rule is only necessary to handle certain zero-length edges (A=B). 343 score := 0 344 if p.Sub(a.Vector).Dot(aTan.Vector) < 0 { 345 score = 2 // B' is on wrong side of A. 346 } else if p.Sub(b.Vector).Dot(bTan.Vector) < 0 { 347 score = 1 // B' is on wrong side of B. 348 } 349 350 if score > 0 { // B' is not in the interior of AB. 351 if b.Z <= 0 { 352 score = 3 // B cannot be projected onto this face. 353 } else { 354 uv = r2.Point{b.X / b.Z, b.Y / b.Z} 355 } 356 } 357 358 return uv, score 359 } 360 361 // updateEndpoint returns the interval with the specified endpoint updated to 362 // the given value. If the value lies beyond the opposite endpoint, nothing is 363 // changed and false is returned. 364 func updateEndpoint(bound r1.Interval, highEndpoint bool, value float64) (r1.Interval, bool) { 365 if !highEndpoint { 366 if bound.Hi < value { 367 return bound, false 368 } 369 if bound.Lo < value { 370 bound.Lo = value 371 } 372 return bound, true 373 } 374 375 if bound.Lo > value { 376 return bound, false 377 } 378 if bound.Hi > value { 379 bound.Hi = value 380 } 381 return bound, true 382 } 383 384 // clipBoundAxis returns the clipped versions of the bounding intervals for the given 385 // axes for the line segment from (a0,a1) to (b0,b1) so that neither extends beyond the 386 // given clip interval. negSlope is a precomputed helper variable that indicates which 387 // diagonal of the bounding box is spanned by AB; it is false if AB has positive slope, 388 // and true if AB has negative slope. If the clipping interval doesn't overlap the bounds, 389 // false is returned. 390 func clipBoundAxis(a0, b0 float64, bound0 r1.Interval, a1, b1 float64, bound1 r1.Interval, 391 negSlope bool, clip r1.Interval) (bound0c, bound1c r1.Interval, updated bool) { 392 393 if bound0.Lo < clip.Lo { 394 // If the upper bound is below the clips lower bound, there is nothing to do. 395 if bound0.Hi < clip.Lo { 396 return bound0, bound1, false 397 } 398 // narrow the intervals lower bound to the clip bound. 399 bound0.Lo = clip.Lo 400 if bound1, updated = updateEndpoint(bound1, negSlope, interpolateFloat64(clip.Lo, a0, b0, a1, b1)); !updated { 401 return bound0, bound1, false 402 } 403 } 404 405 if bound0.Hi > clip.Hi { 406 // If the lower bound is above the clips upper bound, there is nothing to do. 407 if bound0.Lo > clip.Hi { 408 return bound0, bound1, false 409 } 410 // narrow the intervals upper bound to the clip bound. 411 bound0.Hi = clip.Hi 412 if bound1, updated = updateEndpoint(bound1, !negSlope, interpolateFloat64(clip.Hi, a0, b0, a1, b1)); !updated { 413 return bound0, bound1, false 414 } 415 } 416 return bound0, bound1, true 417 } 418 419 // edgeIntersectsRect reports whether the edge defined by AB intersects the 420 // given closed rectangle to within the error bound. 421 func edgeIntersectsRect(a, b r2.Point, r r2.Rect) bool { 422 // First check whether the bounds of a Rect around AB intersects the given rect. 423 if !r.Intersects(r2.RectFromPoints(a, b)) { 424 return false 425 } 426 427 // Otherwise AB intersects the rect if and only if all four vertices of rect 428 // do not lie on the same side of the extended line AB. We test this by finding 429 // the two vertices of rect with minimum and maximum projections onto the normal 430 // of AB, and computing their dot products with the edge normal. 431 n := b.Sub(a).Ortho() 432 433 i := 0 434 if n.X >= 0 { 435 i = 1 436 } 437 j := 0 438 if n.Y >= 0 { 439 j = 1 440 } 441 442 max := n.Dot(r.VertexIJ(i, j).Sub(a)) 443 min := n.Dot(r.VertexIJ(1-i, 1-j).Sub(a)) 444 445 return (max >= 0) && (min <= 0) 446 } 447 448 // clippedEdgeBound returns the bounding rectangle of the portion of the edge defined 449 // by AB intersected by clip. The resulting bound may be empty. This is a convenience 450 // function built on top of clipEdgeBound. 451 func clippedEdgeBound(a, b r2.Point, clip r2.Rect) r2.Rect { 452 bound := r2.RectFromPoints(a, b) 453 if b1, intersects := clipEdgeBound(a, b, clip, bound); intersects { 454 return b1 455 } 456 return r2.EmptyRect() 457 } 458 459 // clipEdgeBound clips an edge AB to sequence of rectangles efficiently. 460 // It represents the clipped edges by their bounding boxes rather than as a pair of 461 // endpoints. Specifically, let A'B' be some portion of an edge AB, and let bound be 462 // a tight bound of A'B'. This function returns the bound that is a tight bound 463 // of A'B' intersected with a given rectangle. If A'B' does not intersect clip, 464 // it returns false and the original bound. 465 func clipEdgeBound(a, b r2.Point, clip, bound r2.Rect) (r2.Rect, bool) { 466 // negSlope indicates which diagonal of the bounding box is spanned by AB: it 467 // is false if AB has positive slope, and true if AB has negative slope. This is 468 // used to determine which interval endpoints need to be updated each time 469 // the edge is clipped. 470 negSlope := (a.X > b.X) != (a.Y > b.Y) 471 472 b0x, b0y, up1 := clipBoundAxis(a.X, b.X, bound.X, a.Y, b.Y, bound.Y, negSlope, clip.X) 473 if !up1 { 474 return bound, false 475 } 476 b1y, b1x, up2 := clipBoundAxis(a.Y, b.Y, b0y, a.X, b.X, b0x, negSlope, clip.Y) 477 if !up2 { 478 return r2.Rect{b0x, b0y}, false 479 } 480 return r2.Rect{X: b1x, Y: b1y}, true 481 } 482 483 // interpolateFloat64 returns a value with the same combination of a1 and b1 as the 484 // given value x is of a and b. This function makes the following guarantees: 485 // - If x == a, then x1 = a1 (exactly). 486 // - If x == b, then x1 = b1 (exactly). 487 // - If a <= x <= b, then a1 <= x1 <= b1 (even if a1 == b1). 488 // This requires a != b. 489 func interpolateFloat64(x, a, b, a1, b1 float64) float64 { 490 // To get results that are accurate near both A and B, we interpolate 491 // starting from the closer of the two points. 492 if math.Abs(a-x) <= math.Abs(b-x) { 493 return a1 + (b1-a1)*(x-a)/(b-a) 494 } 495 return b1 + (a1-b1)*(x-b)/(a-b) 496 } 497 498 // FaceSegment represents an edge AB clipped to an S2 cube face. It is 499 // represented by a face index and a pair of (u,v) coordinates. 500 type FaceSegment struct { 501 face int 502 a, b r2.Point 503 } 504 505 // FaceSegments subdivides the given edge AB at every point where it crosses the 506 // boundary between two S2 cube faces and returns the corresponding FaceSegments. 507 // The segments are returned in order from A toward B. The input points must be 508 // unit length. 509 // 510 // This function guarantees that the returned segments form a continuous path 511 // from A to B, and that all vertices are within faceClipErrorUVDist of the 512 // line AB. All vertices lie within the [-1,1]x[-1,1] cube face rectangles. 513 // The results are consistent with Sign, i.e. the edge is well-defined even its 514 // endpoints are antipodal. 515 // TODO(roberts): Extend the implementation of PointCross so that this is true. 516 func FaceSegments(a, b Point) []FaceSegment { 517 var segment FaceSegment 518 519 // Fast path: both endpoints are on the same face. 520 var aFace, bFace int 521 aFace, segment.a.X, segment.a.Y = xyzToFaceUV(a.Vector) 522 bFace, segment.b.X, segment.b.Y = xyzToFaceUV(b.Vector) 523 if aFace == bFace { 524 segment.face = aFace 525 return []FaceSegment{segment} 526 } 527 528 // Starting at A, we follow AB from face to face until we reach the face 529 // containing B. The following code is designed to ensure that we always 530 // reach B, even in the presence of numerical errors. 531 // 532 // First we compute the normal to the plane containing A and B. This normal 533 // becomes the ultimate definition of the line AB; it is used to resolve all 534 // questions regarding where exactly the line goes. Unfortunately due to 535 // numerical errors, the line may not quite intersect the faces containing 536 // the original endpoints. We handle this by moving A and/or B slightly if 537 // necessary so that they are on faces intersected by the line AB. 538 ab := a.PointCross(b) 539 540 aFace, segment.a = moveOriginToValidFace(aFace, a, ab, segment.a) 541 bFace, segment.b = moveOriginToValidFace(bFace, b, Point{ab.Mul(-1)}, segment.b) 542 543 // Now we simply follow AB from face to face until we reach B. 544 var segments []FaceSegment 545 segment.face = aFace 546 bSaved := segment.b 547 548 for face := aFace; face != bFace; { 549 // Complete the current segment by finding the point where AB 550 // exits the current face. 551 z := faceXYZtoUVW(face, ab) 552 n := pointUVW{z.Vector} 553 554 exitAxis := n.exitAxis() 555 segment.b = n.exitPoint(exitAxis) 556 segments = append(segments, segment) 557 558 // Compute the next face intersected by AB, and translate the exit 559 // point of the current segment into the (u,v) coordinates of the 560 // next face. This becomes the first point of the next segment. 561 exitXyz := faceUVToXYZ(face, segment.b.X, segment.b.Y) 562 face = nextFace(face, segment.b, exitAxis, n, bFace) 563 exitUvw := faceXYZtoUVW(face, Point{exitXyz}) 564 segment.face = face 565 segment.a = r2.Point{exitUvw.X, exitUvw.Y} 566 } 567 // Finish the last segment. 568 segment.b = bSaved 569 return append(segments, segment) 570 } 571 572 // moveOriginToValidFace updates the origin point to a valid face if necessary. 573 // Given a line segment AB whose origin A has been projected onto a given cube 574 // face, determine whether it is necessary to project A onto a different face 575 // instead. This can happen because the normal of the line AB is not computed 576 // exactly, so that the line AB (defined as the set of points perpendicular to 577 // the normal) may not intersect the cube face containing A. Even if it does 578 // intersect the face, the exit point of the line from that face may be on 579 // the wrong side of A (i.e., in the direction away from B). If this happens, 580 // we reproject A onto the adjacent face where the line AB approaches A most 581 // closely. This moves the origin by a small amount, but never more than the 582 // error tolerances. 583 func moveOriginToValidFace(face int, a, ab Point, aUV r2.Point) (int, r2.Point) { 584 // Fast path: if the origin is sufficiently far inside the face, it is 585 // always safe to use it. 586 const maxSafeUVCoord = 1 - faceClipErrorUVCoord 587 if math.Max(math.Abs((aUV).X), math.Abs((aUV).Y)) <= maxSafeUVCoord { 588 return face, aUV 589 } 590 591 // Otherwise check whether the normal AB even intersects this face. 592 z := faceXYZtoUVW(face, ab) 593 n := pointUVW{z.Vector} 594 if n.intersectsFace() { 595 // Check whether the point where the line AB exits this face is on the 596 // wrong side of A (by more than the acceptable error tolerance). 597 uv := n.exitPoint(n.exitAxis()) 598 exit := faceUVToXYZ(face, uv.X, uv.Y) 599 aTangent := ab.Normalize().Cross(a.Vector) 600 601 // We can use the given face. 602 if exit.Sub(a.Vector).Dot(aTangent) >= -faceClipErrorRadians { 603 return face, aUV 604 } 605 } 606 607 // Otherwise we reproject A to the nearest adjacent face. (If line AB does 608 // not pass through a given face, it must pass through all adjacent faces.) 609 var dir int 610 if math.Abs((aUV).X) >= math.Abs((aUV).Y) { 611 // U-axis 612 if aUV.X > 0 { 613 dir = 1 614 } 615 face = uvwFace(face, 0, dir) 616 } else { 617 // V-axis 618 if aUV.Y > 0 { 619 dir = 1 620 } 621 face = uvwFace(face, 1, dir) 622 } 623 624 aUV.X, aUV.Y = validFaceXYZToUV(face, a.Vector) 625 aUV.X = math.Max(-1.0, math.Min(1.0, aUV.X)) 626 aUV.Y = math.Max(-1.0, math.Min(1.0, aUV.Y)) 627 628 return face, aUV 629 } 630 631 // nextFace returns the next face that should be visited by FaceSegments, given that 632 // we have just visited face and we are following the line AB (represented 633 // by its normal N in the (u,v,w) coordinates of that face). The other 634 // arguments include the point where AB exits face, the corresponding 635 // exit axis, and the target face containing the destination point B. 636 func nextFace(face int, exit r2.Point, axis axis, n pointUVW, targetFace int) int { 637 // this bit is to work around C++ cleverly casting bools to ints for you. 638 exitA := exit.X 639 exit1MinusA := exit.Y 640 641 if axis == axisV { 642 exitA = exit.Y 643 exit1MinusA = exit.X 644 } 645 exitAPos := 0 646 if exitA > 0 { 647 exitAPos = 1 648 } 649 exit1MinusAPos := 0 650 if exit1MinusA > 0 { 651 exit1MinusAPos = 1 652 } 653 654 // We return the face that is adjacent to the exit point along the given 655 // axis. If line AB exits *exactly* through a corner of the face, there are 656 // two possible next faces. If one is the target face containing B, then 657 // we guarantee that we advance to that face directly. 658 // 659 // The three conditions below check that (1) AB exits approximately through 660 // a corner, (2) the adjacent face along the non-exit axis is the target 661 // face, and (3) AB exits *exactly* through the corner. (The sumEqual 662 // code checks whether the dot product of (u,v,1) and n is exactly zero.) 663 if math.Abs(exit1MinusA) == 1 && 664 uvwFace(face, int(1-axis), exit1MinusAPos) == targetFace && 665 sumEqual(exit.X*n.X, exit.Y*n.Y, -n.Z) { 666 return targetFace 667 } 668 669 // Otherwise return the face that is adjacent to the exit point in the 670 // direction of the exit axis. 671 return uvwFace(face, int(axis), exitAPos) 672 }