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