Coverage Report

Created: 2026-07-15 06:25

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/s2edge_distances.h
Line
Count
Source
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
// Author: ericv@google.com (Eric Veach)
17
//
18
// Defines a collection of functions for computing the distance to an edge,
19
// interpolating along an edge, projecting points onto edges, etc.
20
21
#ifndef S2_S2EDGE_DISTANCES_H_
22
#define S2_S2EDGE_DISTANCES_H_
23
24
#include <cfloat>
25
#include <utility>
26
27
#include "absl/base/macros.h"
28
#include "absl/log/absl_check.h"
29
#include "s2/_fp_contract_off.h"  // IWYU pragma: keep
30
#include "s2/s1angle.h"
31
#include "s2/s1chord_angle.h"
32
#include "s2/s2edge_crossings.h"
33
#include "s2/s2point.h"
34
#include "s2/s2pointutil.h"
35
#include "s2/s2predicates_internal.h"
36
37
namespace S2 {
38
39
/////////////////////////////////////////////////////////////////////////////
40
///////////////            (point, edge) functions            ///////////////
41
42
// Returns the minimum distance from X to any point on the edge AB.  All
43
// arguments should be unit length.  The result is very accurate for small
44
// distances but may have some numerical error if the distance is large
45
// (approximately Pi/2 or greater).  The case A == B is handled correctly.
46
//
47
// If you want to compare a distance against a fixed threshold, e.g.
48
//    if (S2::GetDistance(x, a, b) < limit)
49
// then it is significantly faster to use UpdateMinDistance() below.
50
S1Angle GetDistance(const S2Point& x, const S2Point& a, const S2Point& b);
51
52
// Returns true if the distance from X to the edge AB is less than "limit".
53
// (Specify limit.Successor() for "less than or equal to".)  This method is
54
// significantly faster than GetDistance().  If you want to compare against a
55
// fixed S1Angle, you should convert it to an S1ChordAngle once and save the
56
// value, since this step is relatively expensive.
57
//
58
// See s2pred::CompareEdgeDistance() for an exact version of this predicate.
59
bool IsDistanceLess(const S2Point& x, const S2Point& a, const S2Point& b,
60
                    S1ChordAngle limit);
61
62
// If the distance from X to the edge AB is less than "min_dist", this
63
// method updates "min_dist" and returns true.  Otherwise it returns false.
64
// The case A == B is handled correctly.
65
//
66
// Use this method when you want to compute many distances and keep track of
67
// the minimum.  It is significantly faster than using GetDistance(),
68
// because (1) using S1ChordAngle is much faster than S1Angle, and (2) it
69
// can save a lot of work by not actually computing the distance when it is
70
// obviously larger than the current minimum.
71
bool UpdateMinDistance(const S2Point& x, const S2Point& a, const S2Point& b,
72
                       S1ChordAngle* min_dist);
73
74
// If the maximum distance from X to the edge AB is greater than "max_dist",
75
// this method updates "max_dist" and returns true.  Otherwise it returns false.
76
// The case A == B is handled correctly.
77
bool UpdateMaxDistance(const S2Point& x, const S2Point& a, const S2Point& b,
78
                       S1ChordAngle* max_dist);
79
80
// Returns the maximum error in the result of UpdateMinDistance (and
81
// associated functions such as UpdateMinInteriorDistance, IsDistanceLess,
82
// etc), assuming that all input points are normalized to within the bounds
83
// guaranteed by S2Point::Normalize().  The error can be added or subtracted
84
// from an S1ChordAngle "x" using x.PlusError(error).
85
//
86
// Note that accuracy goes down as the distance approaches 0 degrees or 180
87
// degrees (for different reasons).  Near 0 degrees the error is acceptable
88
// for all practical purposes (about 1.2e-15 radians ~= 8 nanometers).  For
89
// exactly antipodal points the maximum error is quite high (0.5 meters), but
90
// this error drops rapidly as the points move away from antipodality
91
// (approximately 1 millimeter for points that are 50 meters from antipodal,
92
// and 1 micrometer for points that are 50km from antipodal).
93
double GetUpdateMinDistanceMaxError(S1ChordAngle dist);
94
95
// Returns true if the minimum distance from X to the edge AB is attained at
96
// an interior point of AB (i.e., not an endpoint), and that distance is less
97
// than "limit".  (Specify limit.Successor() for "less than or equal to".)
98
bool IsInteriorDistanceLess(const S2Point& x,
99
                            const S2Point& a, const S2Point& b,
100
                            S1ChordAngle limit);
101
102
// If the minimum distance from X to AB is attained at an interior point of AB
103
// (i.e., not an endpoint), and that distance is less than "min_dist", then
104
// this method updates "min_dist" and returns true.  Otherwise returns false.
105
bool UpdateMinInteriorDistance(const S2Point& x,
106
                               const S2Point& a, const S2Point& b,
107
                               S1ChordAngle* min_dist);
108
109
// Returns the point along the edge AB that is closest to the point X.
110
// The fractional distance of this point along the edge AB can be obtained
111
// using GetDistanceFraction() above.  Requires that all vectors have
112
// unit length.
113
S2Point Project(const S2Point& x, const S2Point& a, const S2Point& b);
114
115
// A slightly more efficient version of Project() where the cross product of
116
// the two endpoints has been precomputed.  The cross product does not need to
117
// be normalized, but should be computed using S2::RobustCrossProd() for the
118
// most accurate results.  Requires that x, a, and b have unit length.
119
S2Point Project(const S2Point& x, const S2Point& a, const S2Point& b,
120
                const Vector3_d& a_cross_b);
121
122
// kProjectPerpendicularError is an upper bound on the distance from the point
123
// returned by Project() to the edge AB.  Note that it only bounds the error
124
// perpendicular to the edge, not the error parallel to it.
125
constexpr S1Angle kProjectPerpendicularError = S1Angle::Radians(
126
    (2 + (2 / s2pred::kSqrt3)) * s2pred::DBL_ERR) + S2::kRobustCrossProdError;
127
128
/////////////////////////////////////////////////////////////////////////////
129
///////////////         (point along edge) functions          ///////////////
130
131
// Given a point X and an edge AB, returns the distance ratio AX / (AX + BX).
132
// If X happens to be on the line segment AB, this is the fraction "t" such
133
// that X == Interpolate(A, B, t).  Requires that A and B are distinct.
134
double GetDistanceFraction(const S2Point& x,
135
                           const S2Point& a, const S2Point& b);
136
137
// Returns the point X along the line segment AB whose distance from A is the
138
// given fraction "t" of the distance AB (where "t" is not necessarily in the
139
// range [0, 1]).  Note that all distances are measured on the surface of the
140
// sphere, so this is more complicated than just computing (1-t)*a + t*b and
141
// normalizing the result.
142
//
143
// Note that the line AB has a well-defined direction even when A and B are
144
// antipodal or nearly so (see S2::RobustCrossProd).
145
S2Point Interpolate(const S2Point& a, const S2Point& b, double t);
146
147
// Returns the point at distance "r" from A along the line AB.
148
//
149
// Note that the line AB has a well-defined direction even when A and B are
150
// antipodal or nearly so.  If A == B then an arbitrary direction is chosen.
151
S2Point GetPointOnLine(const S2Point& a, const S2Point& b, S1Angle r);
152
153
// Faster than the function above, but cannot accurately represent distances
154
// near 180 degrees due to the limitations of S1ChordAngle.
155
S2Point GetPointOnLine(const S2Point& a, const S2Point& b, S1ChordAngle r);
156
157
// Returns S2Point to the left of the edge from `a` to `b` which
158
// is distance 'r' away from `a` orthogonal to the specified edge.
159
//   c (result)
160
//   |
161
//   |
162
//   a --------> b
163
S2Point GetPointToLeft(const S2Point& a, const S2Point& b, S1Angle r);
164
165
// Faster than the function above due to the use of S1ChordAngle.
166
S2Point GetPointToLeft(const S2Point& a, const S2Point& b, S1ChordAngle r);
167
168
// Returns S2Point to the right of the edge from `a` to `b` which
169
// is distance 'r' away from `a` orthogonal to the specified edge.
170
//  a --------> b
171
//  |
172
//  |
173
//  c (result)
174
S2Point GetPointToRight(const S2Point& a, const S2Point& b, S1Angle r);
175
176
// Faster than the function above due to the use of S1ChordAngle.
177
S2Point GetPointToRight(const S2Point& a, const S2Point& b, S1ChordAngle r);
178
179
// kGetPointOnLineError is an upper bound on the distance between the point
180
// returned by GetPointOnLine() and the corresponding true infinite-precision
181
// result.
182
constexpr S1Angle kGetPointOnLineError = S1Angle::Radians(
183
    (4 + (2 / s2pred::kSqrt3)) * s2pred::DBL_ERR) + S2::kRobustCrossProdError;
184
185
// Returns the point at distance "r" along the ray with the given origin and
186
// direction.  "dir" is required to be perpendicular to "origin" (since this
187
// is how directions on the sphere are represented).
188
//
189
// This function is similar to S2::GetPointOnLine() except that (1) the first
190
// two arguments are required to be perpendicular and (2) it is much faster.
191
// It can be used as an alternative to repeatedly calling GetPointOnLine() by
192
// computing "dir" as
193
//
194
//   S2Point dir = S2::RobustCrossProd(a, b).CrossProd(a).Normalize();
195
//
196
// REQUIRES: "origin" and "dir" are perpendicular to within the tolerance
197
//           of the calculation above.
198
S2Point GetPointOnRay(const S2Point& origin, const S2Point& dir, S1Angle r);
199
200
// Faster than the function above, but cannot accurately represent distances
201
// near 180 degrees due to the limitations of S1ChordAngle.
202
S2Point GetPointOnRay(const S2Point& origin, const S2Point& dir,
203
                      S1ChordAngle r);
204
205
// kGetPointOnRayPerpendicularError is an upper bound on the distance from the
206
// point returned by GetPointOnRay() to the ray itself.  Note that it only
207
// bounds the error perpendicular to the ray, not the error parallel to it.
208
constexpr S1Angle kGetPointOnRayPerpendicularError = S1Angle::Radians(
209
    3 * s2pred::DBL_ERR);
210
211
/////////////////////////////////////////////////////////////////////////////
212
///////////////            (edge, edge) functions             ///////////////
213
214
215
// Like UpdateMinDistance(), but computes the minimum distance between the
216
// given pair of edges.  (If the two edges cross, the distance is zero.)
217
// The cases a0 == a1 and b0 == b1 are handled correctly.
218
bool UpdateEdgePairMinDistance(const S2Point& a0, const S2Point& a1,
219
                               const S2Point& b0, const S2Point& b1,
220
                               S1ChordAngle* min_dist);
221
222
// As above, but for maximum distances.  If one edge crosses the antipodal
223
// reflection of the other, the distance is Pi.
224
bool UpdateEdgePairMaxDistance(const S2Point& a0, const S2Point& a1,
225
                               const S2Point& b0, const S2Point& b1,
226
                               S1ChordAngle* max_dist);
227
228
// Returns true if the minimum distance between two edges is less than distance.
229
//
230
// See s2pred::CompareEdgePairDistance() for an exact version of this predicate.
231
bool IsEdgePairDistanceLess(const S2Point& a0, const S2Point& a1,
232
                            const S2Point& b0, const S2Point& b1,
233
                            S1ChordAngle distance);
234
235
// Returns the pair of points (a, b) that achieves the minimum distance
236
// between edges a0a1 and b0b1, where "a" is a point on a0a1 and "b" is a
237
// point on b0b1.  If the two edges intersect, "a" and "b" are both equal to
238
// the intersection point.  Handles a0 == a1 and b0 == b1 correctly.
239
std::pair<S2Point, S2Point> GetEdgePairClosestPoints(
240
    const S2Point& a0, const S2Point& a1,
241
    const S2Point& b0, const S2Point& b1);
242
243
// Returns true if every point on edge B=b0b1 is no further than "tolerance"
244
// from some point on edge A=a0a1.  Equivalently, returns true if the directed
245
// Hausdorff distance from B to A is no more than "tolerance".
246
// Requires that tolerance is less than 90 degrees.
247
bool IsEdgeBNearEdgeA(const S2Point& a0, const S2Point& a1,
248
                      const S2Point& b0, const S2Point& b1,
249
                      S1Angle tolerance);
250
251
252
//////////////////   Implementation details follow   ////////////////////
253
254
// TODO(user): Remove this in favor of s2pred::CompareEdgeDistance()?
255
inline bool IsDistanceLess(const S2Point& x, const S2Point& a,
256
0
                           const S2Point& b, S1ChordAngle limit) {
257
0
  return UpdateMinDistance(x, a, b, &limit);
258
0
}
259
260
inline bool IsInteriorDistanceLess(const S2Point& x, const S2Point& a,
261
0
                                   const S2Point& b, S1ChordAngle limit) {
262
0
  return UpdateMinInteriorDistance(x, a, b, &limit);
263
0
}
264
265
inline S2Point GetPointOnRay(const S2Point& origin, const S2Point& dir,
266
0
                             S1ChordAngle r) {
267
0
  ABSL_DCHECK(S2::IsUnitLength(origin));
268
0
  ABSL_DCHECK(S2::IsUnitLength(dir));
269
  // The error bound below includes the error in computing the dot product.
270
0
  ABSL_DCHECK_LE(origin.DotProd(dir),
271
0
                 S2::kRobustCrossProdError.radians() + 0.75 * DBL_EPSILON);
272
273
  // Mathematically the result should already be unit length, but we normalize
274
  // it anyway to ensure that the error is within acceptable bounds.
275
  // (Otherwise errors can build up when the result of one interpolation is
276
  // fed into another interpolation.)
277
  //
278
  // Note that it is much cheaper to compute the sine and cosine of an
279
  // S1ChordAngle than an S1Angle.
280
0
  return (cos(r) * origin + sin(r) * dir).Normalize();
281
0
}
282
283
inline S2Point GetPointOnRay(const S2Point& origin, const S2Point& dir,
284
0
                             const S1Angle r) {
285
  // See comments above.
286
0
  ABSL_DCHECK(S2::IsUnitLength(origin));
287
0
  ABSL_DCHECK(S2::IsUnitLength(dir));
288
0
  ABSL_DCHECK_LE(origin.DotProd(dir),
289
0
                 S2::kRobustCrossProdError.radians() + 0.75 * DBL_EPSILON);
290
291
0
  const S1Angle::SinCosPair trig_r = r.SinCos();
292
0
  return (trig_r.cos * origin + trig_r.sin * dir).Normalize();
293
0
}
294
295
}  // namespace S2
296
297
#endif  // S2_S2EDGE_DISTANCES_H_