• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2005 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.common.geometry;
17 
18 
19 /**
20  * An S1Interval represents a closed interval on a unit circle (also known as a
21  * 1-dimensional sphere). It is capable of representing the empty interval
22  * (containing no points), the full interval (containing all points), and
23  * zero-length intervals (containing a single point).
24  *
25  *  Points are represented by the angle they make with the positive x-axis in
26  * the range [-Pi, Pi]. An interval is represented by its lower and upper bounds
27  * (both inclusive, since the interval is closed). The lower bound may be
28  * greater than the upper bound, in which case the interval is "inverted" (i.e.
29  * it passes through the point (-1, 0)).
30  *
31  *  Note that the point (-1, 0) has two valid representations, Pi and -Pi. The
32  * normalized representation of this point internally is Pi, so that endpoints
33  * of normal intervals are in the range (-Pi, Pi]. However, we take advantage of
34  * the point -Pi to construct two special intervals: the Full() interval is
35  * [-Pi, Pi], and the Empty() interval is [Pi, -Pi].
36  *
37  */
38 
39 public final strictfp class S1Interval implements Cloneable {
40 
41   private final double lo;
42   private final double hi;
43 
44   /**
45    * Both endpoints must be in the range -Pi to Pi inclusive. The value -Pi is
46    * converted internally to Pi except for the Full() and Empty() intervals.
47    */
S1Interval(double lo, double hi)48   public S1Interval(double lo, double hi) {
49     this(lo, hi, false);
50   }
51 
52   /**
53    * Copy constructor. Assumes that the given interval is valid.
54    *
55    * TODO(dbeaumont): Make this class immutable and remove this method.
56    */
S1Interval(S1Interval interval)57   public S1Interval(S1Interval interval) {
58     this.lo = interval.lo;
59     this.hi = interval.hi;
60   }
61 
62   /**
63    * Internal constructor that assumes that both arguments are in the correct
64    * range, i.e. normalization from -Pi to Pi is already done.
65    */
S1Interval(double lo, double hi, boolean checked)66   private S1Interval(double lo, double hi, boolean checked) {
67     double newLo = lo;
68     double newHi = hi;
69     if (!checked) {
70       if (lo == -S2.M_PI && hi != S2.M_PI) {
71         newLo = S2.M_PI;
72       }
73       if (hi == -S2.M_PI && lo != S2.M_PI) {
74         newHi = S2.M_PI;
75       }
76     }
77     this.lo = newLo;
78     this.hi = newHi;
79   }
80 
empty()81   public static S1Interval empty() {
82     return new S1Interval(S2.M_PI, -S2.M_PI, true);
83   }
84 
full()85   public static S1Interval full() {
86     return new S1Interval(-S2.M_PI, S2.M_PI, true);
87   }
88 
89   /** Convenience method to construct an interval containing a single point. */
fromPoint(double p)90   public static S1Interval fromPoint(double p) {
91     if (p == -S2.M_PI) {
92       p = S2.M_PI;
93     }
94     return new S1Interval(p, p, true);
95   }
96 
97   /**
98    * Convenience method to construct the minimal interval containing the two
99    * given points. This is equivalent to starting with an empty interval and
100    * calling AddPoint() twice, but it is more efficient.
101    */
fromPointPair(double p1, double p2)102   public static S1Interval fromPointPair(double p1, double p2) {
103     // assert (Math.abs(p1) <= S2.M_PI && Math.abs(p2) <= S2.M_PI);
104     if (p1 == -S2.M_PI) {
105       p1 = S2.M_PI;
106     }
107     if (p2 == -S2.M_PI) {
108       p2 = S2.M_PI;
109     }
110     if (positiveDistance(p1, p2) <= S2.M_PI) {
111       return new S1Interval(p1, p2, true);
112     } else {
113       return new S1Interval(p2, p1, true);
114     }
115   }
116 
lo()117   public double lo() {
118     return lo;
119   }
120 
hi()121   public double hi() {
122     return hi;
123   }
124 
125   /**
126    * An interval is valid if neither bound exceeds Pi in absolute value, and the
127    * value -Pi appears only in the Empty() and Full() intervals.
128    */
isValid()129   public boolean isValid() {
130     return (Math.abs(lo()) <= S2.M_PI && Math.abs(hi()) <= S2.M_PI
131         && !(lo() == -S2.M_PI && hi() != S2.M_PI) && !(hi() == -S2.M_PI && lo() != S2.M_PI));
132   }
133 
134   /** Return true if the interval contains all points on the unit circle. */
isFull()135   public boolean isFull() {
136     return hi() - lo() == 2 * S2.M_PI;
137   }
138 
139 
140   /** Return true if the interval is empty, i.e. it contains no points. */
isEmpty()141   public boolean isEmpty() {
142     return lo() - hi() == 2 * S2.M_PI;
143   }
144 
145 
146   /* Return true if lo() > hi(). (This is true for empty intervals.) */
isInverted()147   public boolean isInverted() {
148     return lo() > hi();
149   }
150 
151   /**
152    * Return the midpoint of the interval. For full and empty intervals, the
153    * result is arbitrary.
154    */
getCenter()155   public double getCenter() {
156     double center = 0.5 * (lo() + hi());
157     if (!isInverted()) {
158       return center;
159     }
160     // Return the center in the range (-Pi, Pi].
161     return (center <= 0) ? (center + S2.M_PI) : (center - S2.M_PI);
162   }
163 
164   /**
165    * Return the length of the interval. The length of an empty interval is
166    * negative.
167    */
getLength()168   public double getLength() {
169     double length = hi() - lo();
170     if (length >= 0) {
171       return length;
172     }
173     length += 2 * S2.M_PI;
174     // Empty intervals have a negative length.
175     return (length > 0) ? length : -1;
176   }
177 
178   /**
179    * Return the complement of the interior of the interval. An interval and its
180    * complement have the same boundary but do not share any interior values. The
181    * complement operator is not a bijection, since the complement of a singleton
182    * interval (containing a single value) is the same as the complement of an
183    * empty interval.
184    */
complement()185   public S1Interval complement() {
186     if (lo() == hi()) {
187       return full(); // Singleton.
188     }
189     return new S1Interval(hi(), lo(), true); // Handles
190     // empty and
191     // full.
192   }
193 
194   /** Return true if the interval (which is closed) contains the point 'p'. */
contains(double p)195   public boolean contains(double p) {
196     // Works for empty, full, and singleton intervals.
197     // assert (Math.abs(p) <= S2.M_PI);
198     if (p == -S2.M_PI) {
199       p = S2.M_PI;
200     }
201     return fastContains(p);
202   }
203 
204   /**
205    * Return true if the interval (which is closed) contains the point 'p'. Skips
206    * the normalization of 'p' from -Pi to Pi.
207    *
208    */
fastContains(double p)209   public boolean fastContains(double p) {
210     if (isInverted()) {
211       return (p >= lo() || p <= hi()) && !isEmpty();
212     } else {
213       return p >= lo() && p <= hi();
214     }
215   }
216 
217   /** Return true if the interior of the interval contains the point 'p'. */
interiorContains(double p)218   public boolean interiorContains(double p) {
219     // Works for empty, full, and singleton intervals.
220     // assert (Math.abs(p) <= S2.M_PI);
221     if (p == -S2.M_PI) {
222       p = S2.M_PI;
223     }
224 
225     if (isInverted()) {
226       return p > lo() || p < hi();
227     } else {
228       return (p > lo() && p < hi()) || isFull();
229     }
230   }
231 
232   /**
233    * Return true if the interval contains the given interval 'y'. Works for
234    * empty, full, and singleton intervals.
235    */
contains(final S1Interval y)236   public boolean contains(final S1Interval y) {
237     // It might be helpful to compare the structure of these tests to
238     // the simpler Contains(double) method above.
239 
240     if (isInverted()) {
241       if (y.isInverted()) {
242         return y.lo() >= lo() && y.hi() <= hi();
243       }
244       return (y.lo() >= lo() || y.hi() <= hi()) && !isEmpty();
245     } else {
246       if (y.isInverted()) {
247         return isFull() || y.isEmpty();
248       }
249       return y.lo() >= lo() && y.hi() <= hi();
250     }
251   }
252 
253   /**
254    * Returns true if the interior of this interval contains the entire interval
255    * 'y'. Note that x.InteriorContains(x) is true only when x is the empty or
256    * full interval, and x.InteriorContains(S1Interval(p,p)) is equivalent to
257    * x.InteriorContains(p).
258    */
interiorContains(final S1Interval y)259   public boolean interiorContains(final S1Interval y) {
260     if (isInverted()) {
261       if (!y.isInverted()) {
262         return y.lo() > lo() || y.hi() < hi();
263       }
264       return (y.lo() > lo() && y.hi() < hi()) || y.isEmpty();
265     } else {
266       if (y.isInverted()) {
267         return isFull() || y.isEmpty();
268       }
269       return (y.lo() > lo() && y.hi() < hi()) || isFull();
270     }
271   }
272 
273   /**
274    * Return true if the two intervals contain any points in common. Note that
275    * the point +/-Pi has two representations, so the intervals [-Pi,-3] and
276    * [2,Pi] intersect, for example.
277    */
intersects(final S1Interval y)278   public boolean intersects(final S1Interval y) {
279     if (isEmpty() || y.isEmpty()) {
280       return false;
281     }
282     if (isInverted()) {
283       // Every non-empty inverted interval contains Pi.
284       return y.isInverted() || y.lo() <= hi() || y.hi() >= lo();
285     } else {
286       if (y.isInverted()) {
287         return y.lo() <= hi() || y.hi() >= lo();
288       }
289       return y.lo() <= hi() && y.hi() >= lo();
290     }
291   }
292 
293   /**
294    * Return true if the interior of this interval contains any point of the
295    * interval 'y' (including its boundary). Works for empty, full, and singleton
296    * intervals.
297    */
interiorIntersects(final S1Interval y)298   public boolean interiorIntersects(final S1Interval y) {
299     if (isEmpty() || y.isEmpty() || lo() == hi()) {
300       return false;
301     }
302     if (isInverted()) {
303       return y.isInverted() || y.lo() < hi() || y.hi() > lo();
304     } else {
305       if (y.isInverted()) {
306         return y.lo() < hi() || y.hi() > lo();
307       }
308       return (y.lo() < hi() && y.hi() > lo()) || isFull();
309     }
310   }
311 
312   /**
313    * Expand the interval by the minimum amount necessary so that it contains the
314    * given point "p" (an angle in the range [-Pi, Pi]).
315    */
addPoint(double p)316   public S1Interval addPoint(double p) {
317     // assert (Math.abs(p) <= S2.M_PI);
318     if (p == -S2.M_PI) {
319       p = S2.M_PI;
320     }
321 
322     if (fastContains(p)) {
323       return new S1Interval(this);
324     }
325 
326     if (isEmpty()) {
327       return S1Interval.fromPoint(p);
328     } else {
329       // Compute distance from p to each endpoint.
330       double dlo = positiveDistance(p, lo());
331       double dhi = positiveDistance(hi(), p);
332       if (dlo < dhi) {
333         return new S1Interval(p, hi());
334       } else {
335         return new S1Interval(lo(), p);
336       }
337       // Adding a point can never turn a non-full interval into a full one.
338     }
339   }
340 
341   /**
342    * Return an interval that contains all points within a distance "radius" of
343    * a point in this interval. Note that the expansion of an empty interval is
344    * always empty. The radius must be non-negative.
345    */
expanded(double radius)346   public S1Interval expanded(double radius) {
347     // assert (radius >= 0);
348     if (isEmpty()) {
349       return this;
350     }
351 
352     // Check whether this interval will be full after expansion, allowing
353     // for a 1-bit rounding error when computing each endpoint.
354     if (getLength() + 2 * radius >= 2 * S2.M_PI - 1e-15) {
355       return full();
356     }
357 
358     // NOTE(dbeaumont): Should this remainder be 2 * M_PI or just M_PI ??
359     double lo = Math.IEEEremainder(lo() - radius, 2 * S2.M_PI);
360     double hi = Math.IEEEremainder(hi() + radius, 2 * S2.M_PI);
361     if (lo == -S2.M_PI) {
362       lo = S2.M_PI;
363     }
364     return new S1Interval(lo, hi);
365   }
366 
367   /**
368    * Return the smallest interval that contains this interval and the given
369    * interval "y".
370    */
union(final S1Interval y)371   public S1Interval union(final S1Interval y) {
372     // The y.is_full() case is handled correctly in all cases by the code
373     // below, but can follow three separate code paths depending on whether
374     // this interval is inverted, is non-inverted but contains Pi, or neither.
375 
376     if (y.isEmpty()) {
377       return this;
378     }
379     if (fastContains(y.lo())) {
380       if (fastContains(y.hi())) {
381         // Either this interval contains y, or the union of the two
382         // intervals is the Full() interval.
383         if (contains(y)) {
384           return this; // is_full() code path
385         }
386         return full();
387       }
388       return new S1Interval(lo(), y.hi(), true);
389     }
390     if (fastContains(y.hi())) {
391       return new S1Interval(y.lo(), hi(), true);
392     }
393 
394     // This interval contains neither endpoint of y. This means that either y
395     // contains all of this interval, or the two intervals are disjoint.
396     if (isEmpty() || y.fastContains(lo())) {
397       return y;
398     }
399 
400     // Check which pair of endpoints are closer together.
401     double dlo = positiveDistance(y.hi(), lo());
402     double dhi = positiveDistance(hi(), y.lo());
403     if (dlo < dhi) {
404       return new S1Interval(y.lo(), hi(), true);
405     } else {
406       return new S1Interval(lo(), y.hi(), true);
407     }
408   }
409 
410   /**
411    * Return the smallest interval that contains the intersection of this
412    * interval with "y". Note that the region of intersection may consist of two
413    * disjoint intervals.
414    */
intersection(final S1Interval y)415   public S1Interval intersection(final S1Interval y) {
416     // The y.is_full() case is handled correctly in all cases by the code
417     // below, but can follow three separate code paths depending on whether
418     // this interval is inverted, is non-inverted but contains Pi, or neither.
419 
420     if (y.isEmpty()) {
421       return empty();
422     }
423     if (fastContains(y.lo())) {
424       if (fastContains(y.hi())) {
425         // Either this interval contains y, or the region of intersection
426         // consists of two disjoint subintervals. In either case, we want
427         // to return the shorter of the two original intervals.
428         if (y.getLength() < getLength()) {
429           return y; // is_full() code path
430         }
431         return this;
432       }
433       return new S1Interval(y.lo(), hi(), true);
434     }
435     if (fastContains(y.hi())) {
436       return new S1Interval(lo(), y.hi(), true);
437     }
438 
439     // This interval contains neither endpoint of y. This means that either y
440     // contains all of this interval, or the two intervals are disjoint.
441 
442     if (y.fastContains(lo())) {
443       return this; // is_empty() okay here
444     }
445     // assert (!intersects(y));
446     return empty();
447   }
448 
449   /**
450    * Return true if the length of the symmetric difference between the two
451    * intervals is at most the given tolerance.
452    */
approxEquals(final S1Interval y, double maxError)453   public boolean approxEquals(final S1Interval y, double maxError) {
454     if (isEmpty()) {
455       return y.getLength() <= maxError;
456     }
457     if (y.isEmpty()) {
458       return getLength() <= maxError;
459     }
460     return (Math.abs(Math.IEEEremainder(y.lo() - lo(), 2 * S2.M_PI))
461         + Math.abs(Math.IEEEremainder(y.hi() - hi(), 2 * S2.M_PI))) <= maxError;
462   }
463 
approxEquals(final S1Interval y)464   public boolean approxEquals(final S1Interval y) {
465     return approxEquals(y, 1e-9);
466   }
467 
468   /**
469    * Return true if two intervals contains the same set of points.
470    */
471   @Override
equals(Object that)472   public boolean equals(Object that) {
473     if (that instanceof S1Interval) {
474       S1Interval thatInterval = (S1Interval) that;
475       return lo() == thatInterval.lo() && hi() == thatInterval.hi();
476     }
477     return false;
478   }
479 
480   @Override
hashCode()481   public int hashCode() {
482     long value = 17;
483     value = 37 * value + Double.doubleToLongBits(lo());
484     value = 37 * value + Double.doubleToLongBits(hi());
485     return (int) ((value >>> 32) ^ value);
486   }
487 
488   @Override
toString()489   public String toString() {
490     return "[" + this.lo() + ", " + this.hi() + "]";
491   }
492 
493   /**
494    * Compute the distance from "a" to "b" in the range [0, 2*Pi). This is
495    * equivalent to (drem(b - a - S2.M_PI, 2 * S2.M_PI) + S2.M_PI), except that
496    * it is more numerically stable (it does not lose precision for very small
497    * positive distances).
498    */
positiveDistance(double a, double b)499   public static double positiveDistance(double a, double b) {
500     double d = b - a;
501     if (d >= 0) {
502       return d;
503     }
504     // We want to ensure that if b == Pi and a == (-Pi + eps),
505     // the return result is approximately 2*Pi and not zero.
506     return (b + S2.M_PI) - (a - S2.M_PI);
507   }
508 }
509