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 #include "s2/s1interval.h"
19 
20 #include <algorithm>
21 #include <cfloat>
22 #include <cmath>
23 
24 #include "s2/base/logging.h"
25 
26 using std::fabs;
27 using std::max;
28 
FromPoint(double p)29 S1Interval S1Interval::FromPoint(double p) {
30   if (p == -M_PI) p = M_PI;
31   return S1Interval(p, p, ARGS_CHECKED);
32 }
33 
GetCenter() const34 double S1Interval::GetCenter() const {
35   double center = 0.5 * (lo() + hi());
36   if (!is_inverted()) return center;
37   // Return the center in the range (-Pi, Pi].
38   return (center <= 0) ? (center + M_PI) : (center - M_PI);
39 }
40 
GetLength() const41 double S1Interval::GetLength() const {
42   double length = hi() - lo();
43   if (length >= 0) return length;
44   length += 2 * M_PI;
45   // Empty intervals have a negative length.
46   return (length > 0) ? length : -1;
47 }
48 
Complement() const49 S1Interval S1Interval::Complement() const {
50   if (lo() == hi()) return Full();   // Singleton.
51   return S1Interval(hi(), lo(), ARGS_CHECKED);  // Handles empty and full.
52 }
53 
GetComplementCenter() const54 double S1Interval::GetComplementCenter() const {
55   if (lo() != hi()) {
56     return Complement().GetCenter();
57   } else {  // Singleton.
58     return (hi() <= 0) ? (hi() + M_PI) : (hi() - M_PI);
59   }
60 }
61 
FastContains(double p) const62 bool S1Interval::FastContains(double p) const {
63   if (is_inverted()) {
64     return (p >= lo() || p <= hi()) && !is_empty();
65   } else {
66     return p >= lo() && p <= hi();
67   }
68 }
69 
Contains(double p) const70 bool S1Interval::Contains(double p) const {
71   // Works for empty, full, and singleton intervals.
72   S2_DCHECK_LE(fabs(p), M_PI);
73   if (p == -M_PI) p = M_PI;
74   return FastContains(p);
75 }
76 
InteriorContains(double p) const77 bool S1Interval::InteriorContains(double p) const {
78   // Works for empty, full, and singleton intervals.
79   S2_DCHECK_LE(fabs(p), M_PI);
80   if (p == -M_PI) p = M_PI;
81 
82   if (is_inverted()) {
83     return p > lo() || p < hi();
84   } else {
85     return (p > lo() && p < hi()) || is_full();
86   }
87 }
88 
Contains(const S1Interval & y) const89 bool S1Interval::Contains(const S1Interval& y) const {
90   // It might be helpful to compare the structure of these tests to
91   // the simpler Contains(double) method above.
92 
93   if (is_inverted()) {
94     if (y.is_inverted()) return y.lo() >= lo() && y.hi() <= hi();
95     return (y.lo() >= lo() || y.hi() <= hi()) && !is_empty();
96   } else {
97     if (y.is_inverted()) return is_full() || y.is_empty();
98     return y.lo() >= lo() && y.hi() <= hi();
99   }
100 }
101 
InteriorContains(const S1Interval & y) const102 bool S1Interval::InteriorContains(const S1Interval& y) const {
103   if (is_inverted()) {
104     if (!y.is_inverted()) return y.lo() > lo() || y.hi() < hi();
105     return (y.lo() > lo() && y.hi() < hi()) || y.is_empty();
106   } else {
107     if (y.is_inverted()) return is_full() || y.is_empty();
108     return (y.lo() > lo() && y.hi() < hi()) || is_full();
109   }
110 }
111 
Intersects(const S1Interval & y) const112 bool S1Interval::Intersects(const S1Interval& y) const {
113   if (is_empty() || y.is_empty()) return false;
114   if (is_inverted()) {
115     // Every non-empty inverted interval contains Pi.
116     return y.is_inverted() || y.lo() <= hi() || y.hi() >= lo();
117   } else {
118     if (y.is_inverted()) return y.lo() <= hi() || y.hi() >= lo();
119     return y.lo() <= hi() && y.hi() >= lo();
120   }
121 }
122 
InteriorIntersects(const S1Interval & y) const123 bool S1Interval::InteriorIntersects(const S1Interval& y) const {
124   if (is_empty() || y.is_empty() || lo() == hi()) return false;
125   if (is_inverted()) {
126     return y.is_inverted() || y.lo() < hi() || y.hi() > lo();
127   } else {
128     if (y.is_inverted()) return y.lo() < hi() || y.hi() > lo();
129     return (y.lo() < hi() && y.hi() > lo()) || is_full();
130   }
131 }
132 
PositiveDistance(double a,double b)133 inline static double PositiveDistance(double a, double b) {
134   // Compute the distance from "a" to "b" in the range [0, 2*Pi).
135   // This is equivalent to (remainder(b - a - M_PI, 2 * M_PI) + M_PI),
136   // except that it is more numerically stable (it does not lose
137   // precision for very small positive distances).
138   double d = b - a;
139   if (d >= 0) return d;
140   // We want to ensure that if b == Pi and a == (-Pi + eps),
141   // the return result is approximately 2*Pi and not zero.
142   return (b + M_PI) - (a - M_PI);
143 }
144 
GetDirectedHausdorffDistance(const S1Interval & y) const145 double S1Interval::GetDirectedHausdorffDistance(const S1Interval& y) const {
146   if (y.Contains(*this)) return 0.0;  // this includes the case *this is empty
147   if (y.is_empty()) return M_PI;  // maximum possible distance on S1
148 
149   double y_complement_center = y.GetComplementCenter();
150   if (Contains(y_complement_center)) {
151     return PositiveDistance(y.hi(), y_complement_center);
152   } else {
153     // The Hausdorff distance is realized by either two hi() endpoints or two
154     // lo() endpoints, whichever is farther apart.
155     double hi_hi = S1Interval(y.hi(), y_complement_center).Contains(hi()) ?
156         PositiveDistance(y.hi(), hi()) : 0;
157     double lo_lo = S1Interval(y_complement_center, y.lo()).Contains(lo()) ?
158         PositiveDistance(lo(), y.lo()) : 0;
159     S2_DCHECK(hi_hi > 0 || lo_lo > 0);
160     return max(hi_hi, lo_lo);
161   }
162 }
163 
AddPoint(double p)164 void S1Interval::AddPoint(double p) {
165   S2_DCHECK_LE(fabs(p), M_PI);
166   if (p == -M_PI) p = M_PI;
167 
168   if (FastContains(p)) return;
169   if (is_empty()) {
170     set_hi(p);
171     set_lo(p);
172   } else {
173     // Compute distance from p to each endpoint.
174     double dlo = PositiveDistance(p, lo());
175     double dhi = PositiveDistance(hi(), p);
176     if (dlo < dhi) {
177       set_lo(p);
178     } else {
179       set_hi(p);
180     }
181     // Adding a point can never turn a non-full interval into a full one.
182   }
183 }
184 
Project(double p) const185 double S1Interval::Project(double p) const {
186   S2_DCHECK(!is_empty());
187   S2_DCHECK_LE(fabs(p), M_PI);
188   if (p == -M_PI) p = M_PI;
189   if (FastContains(p)) return p;
190   // Compute distance from p to each endpoint.
191   double dlo = PositiveDistance(p, lo());
192   double dhi = PositiveDistance(hi(), p);
193   return (dlo < dhi) ? lo() : hi();
194 }
195 
FromPointPair(double p1,double p2)196 S1Interval S1Interval::FromPointPair(double p1, double p2) {
197   S2_DCHECK_LE(fabs(p1), M_PI);
198   S2_DCHECK_LE(fabs(p2), M_PI);
199   if (p1 == -M_PI) p1 = M_PI;
200   if (p2 == -M_PI) p2 = M_PI;
201   if (PositiveDistance(p1, p2) <= M_PI) {
202     return S1Interval(p1, p2, ARGS_CHECKED);
203   } else {
204     return S1Interval(p2, p1, ARGS_CHECKED);
205   }
206 }
207 
Expanded(double margin) const208 S1Interval S1Interval::Expanded(double margin) const {
209   if (margin >= 0) {
210     if (is_empty()) return *this;
211     // Check whether this interval will be full after expansion, allowing
212     // for a 1-bit rounding error when computing each endpoint.
213     if (GetLength() + 2 * margin + 2 * DBL_EPSILON >= 2 * M_PI) return Full();
214   } else {
215     if (is_full()) return *this;
216     // Check whether this interval will be empty after expansion, allowing
217     // for a 1-bit rounding error when computing each endpoint.
218     if (GetLength() + 2 * margin - 2 * DBL_EPSILON <= 0) return Empty();
219   }
220   S1Interval result(remainder(lo() - margin, 2*M_PI),
221                     remainder(hi() + margin, 2*M_PI));
222   if (result.lo() <= -M_PI) result.set_lo(M_PI);
223   return result;
224 }
225 
Union(const S1Interval & y) const226 S1Interval S1Interval::Union(const S1Interval& y) const {
227   // The y.is_full() case is handled correctly in all cases by the code
228   // below, but can follow three separate code paths depending on whether
229   // this interval is inverted, is non-inverted but contains Pi, or neither.
230 
231   if (y.is_empty()) return *this;
232   if (FastContains(y.lo())) {
233     if (FastContains(y.hi())) {
234       // Either this interval contains y, or the union of the two
235       // intervals is the Full() interval.
236       if (Contains(y)) return *this;  // is_full() code path
237       return Full();
238     }
239     return S1Interval(lo(), y.hi(), ARGS_CHECKED);
240   }
241   if (FastContains(y.hi())) return S1Interval(y.lo(), hi(), ARGS_CHECKED);
242 
243   // This interval contains neither endpoint of y.  This means that either y
244   // contains all of this interval, or the two intervals are disjoint.
245   if (is_empty() || y.FastContains(lo())) return y;
246 
247   // Check which pair of endpoints are closer together.
248   double dlo = PositiveDistance(y.hi(), lo());
249   double dhi = PositiveDistance(hi(), y.lo());
250   if (dlo < dhi) {
251     return S1Interval(y.lo(), hi(), ARGS_CHECKED);
252   } else {
253     return S1Interval(lo(), y.hi(), ARGS_CHECKED);
254   }
255 }
256 
Intersection(const S1Interval & y) const257 S1Interval S1Interval::Intersection(const S1Interval& y) const {
258   // The y.is_full() case is handled correctly in all cases by the code
259   // below, but can follow three separate code paths depending on whether
260   // this interval is inverted, is non-inverted but contains Pi, or neither.
261 
262   if (y.is_empty()) return Empty();
263   if (FastContains(y.lo())) {
264     if (FastContains(y.hi())) {
265       // Either this interval contains y, or the region of intersection
266       // consists of two disjoint subintervals.  In either case, we want
267       // to return the shorter of the two original intervals.
268       if (y.GetLength() < GetLength()) return y;  // is_full() code path
269       return *this;
270     }
271     return S1Interval(y.lo(), hi(), ARGS_CHECKED);
272   }
273   if (FastContains(y.hi())) return S1Interval(lo(), y.hi(), ARGS_CHECKED);
274 
275   // This interval contains neither endpoint of y.  This means that either y
276   // contains all of this interval, or the two intervals are disjoint.
277 
278   if (y.FastContains(lo())) return *this;  // is_empty() okay here
279   S2_DCHECK(!Intersects(y));
280   return Empty();
281 }
282 
ApproxEquals(const S1Interval & y,double max_error) const283 bool S1Interval::ApproxEquals(const S1Interval& y, double max_error) const {
284   // Full and empty intervals require special cases because the "endpoints"
285   // are considered to be positioned arbitrarily.
286   if (is_empty()) return y.GetLength() <= 2 * max_error;
287   if (y.is_empty()) return GetLength() <= 2 * max_error;
288   if (is_full()) return y.GetLength() >= 2 * (M_PI - max_error);
289   if (y.is_full()) return GetLength() >= 2 * (M_PI - max_error);
290 
291   // The purpose of the last test below is to verify that moving the endpoints
292   // does not invert the interval, e.g. [-1e20, 1e20] vs. [1e20, -1e20].
293   return (fabs(remainder(y.lo() - lo(), 2 * M_PI)) <= max_error &&
294           fabs(remainder(y.hi() - hi(), 2 * M_PI)) <= max_error &&
295           fabs(GetLength() - y.GetLength()) <= 2 * max_error);
296 }
297