1 /** 2 Represents a closed, bounded interval on the real line. 3 4 Copyright: 2005 Google Inc. All Rights Reserved. 5 6 License: 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 $(LINK http://www.apache.org/licenses/LICENSE-2.0) 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS-IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 19 Authors: ericv@google.com (Eric Veach), madric@gmail.com (Vijay Nayar) 20 */ 21 module s2.r1interval; 22 23 import s2.util.math.vector; 24 import algorithm = std.algorithm; 25 import conv = std.conv; 26 import math = std.math; 27 28 /** 29 An R1Interval represents a closed, bounded interval on the real line. 30 It is capable of representing the empty interval (containing no points) 31 and zero-length intervals (containing a single point). 32 33 This class is intended to be copied by value as desired. It uses 34 the default copy constructor and assignment operator. 35 */ 36 struct R1Interval { 37 public: 38 /// Constructor. If lo > hi, the interval is empty. 39 this(double lo, double hi) { 40 _bounds = Vector2_d([lo, hi]); 41 } 42 43 /// Returns an empty interval. 44 static R1Interval empty() { 45 return R1Interval(); 46 } 47 48 /// Convenience method to construct an interval containing a single point. 49 static R1Interval fromPoint(double p) { 50 return R1Interval(p, p); 51 } 52 53 /** 54 Convenience method to construct the minimal interval containing 55 the two given points. This is equivalent to starting with an empty 56 interval and calling AddPoint() twice, but it is more efficient. 57 */ 58 static R1Interval fromPointPair(double p1, double p2) { 59 if (p1 <= p2) { 60 return R1Interval(p1, p2); 61 } else { 62 return R1Interval(p2, p1); 63 } 64 } 65 66 // Accessors methods. 67 @property 68 double lo() const { 69 return _bounds[0]; 70 } 71 72 @property 73 double hi() const { 74 return _bounds[1]; 75 } 76 77 // Methods to modify one endpoint of an existing R1Interval. Do not use 78 // these methods if you want to replace both endpoints of the interval; use 79 // a constructor instead. For example: 80 // 81 // *lat_bounds = R1Interval(lat_lo, lat_hi); 82 void setLo(double p) { 83 _bounds[0] = p; 84 } 85 86 void setHi(double p) { 87 _bounds[1] = p; 88 } 89 90 /** 91 Methods that allow the R1Interval to be accessed as a vector. (The 92 recommended style is to use lo() and hi() whenever possible, but these 93 methods are useful when the endpoint to be selected is not constant.) 94 */ 95 ref inout(double) opIndex(size_t i) inout { 96 return _bounds[i]; 97 } 98 99 void opIndexAssign(in double v, size_t i) { 100 _bounds[i] = v; 101 } 102 103 Vector2_d bounds() const { 104 return _bounds; 105 } 106 107 ref Vector2_d mutableBounds() return { 108 return _bounds; 109 } 110 111 /// Return true if the interval is empty, i.e. it contains no points. 112 bool isEmpty() const { 113 return lo() > hi(); 114 } 115 116 /// Returns the center of the interval. For empty intervals, the result is arbitrary. 117 double getCenter() const { 118 return 0.5 * (lo() + hi()); 119 } 120 121 /// Returns the length of the interval. The length of an empty interval is negative. 122 double getLength() const { 123 return hi() - lo(); 124 } 125 126 bool contains(double p) const { 127 return p >= lo() && p <= hi(); 128 } 129 130 bool interiorContains(double p) const { 131 return p > lo() && p < hi(); 132 } 133 134 /// Returns true if this interval contains the interval 'y'. 135 bool contains(in R1Interval y) const { 136 if (y.isEmpty()) { 137 return true; 138 } 139 return y.lo() >= lo() && y.hi() <= hi(); 140 } 141 142 /// Returns true if the interior of this interval contains the entire interval 'y' (including its 143 /// boundary). 144 bool interiorContains(in R1Interval y) const { 145 if (y.isEmpty()) { 146 return true; 147 } 148 return y.lo() > lo() && y.hi() < hi(); 149 } 150 151 /// Returns true if this interval intersects the given interval, i.e. if they have any points in 152 /// common. 153 bool intersects(in R1Interval y) const { 154 if (lo() <= y.lo()) { 155 return y.lo() <= hi() && y.lo() <= y.hi(); 156 } else { 157 return lo() <= y.hi() && lo() <= hi(); 158 } 159 } 160 161 /// Returns true if the interior of this interval intersects any point of the given interval 162 /// (including its boundary). 163 bool interiorIntersects(in R1Interval y) const { 164 return y.lo() < hi() && lo() < y.hi() && lo() < hi() && y.lo() <= y.hi(); 165 } 166 167 /// Returns the Hausdorff distance to the given interval 'y'. For two 168 /// R1Intervals x and y, this distance is defined as 169 /// `h(x, y) = max_{p in x} min_{q in y} d(p, q)`. 170 double getDirectedHausdorffDistance(in R1Interval y) const { 171 if (isEmpty()) { 172 return 0.0; 173 } else if (y.isEmpty()) { 174 return double.max; 175 } else { 176 return algorithm.max(0.0, algorithm.max(hi() - y.hi(), y.lo() - lo())); 177 } 178 } 179 180 /// Expands the interval so that it contains the given point "p". 181 void addPoint(double p) { 182 if (isEmpty()) { 183 setLo(p); 184 setHi(p); 185 } else if (p < lo()) { 186 setLo(p); 187 } else if (p > hi()) { 188 setHi(p); 189 } 190 } 191 192 /// Expands the interval so that it contains the given interval "y". 193 void addInterval(in R1Interval y) { 194 if (y.isEmpty()) { 195 return; 196 } else if (isEmpty()) { 197 this = y; 198 return; 199 } 200 if (y.lo() < lo()) { 201 setLo(y.lo()); 202 } 203 if (y.hi() > hi()) { 204 setHi(y.hi()); 205 } 206 } 207 208 /// Returns the closest point in the interval to the given point "p". The interval must be 209 // non-empty. 210 double project(double p) const 211 in { 212 assert(!isEmpty()); 213 } do { 214 return algorithm.max(lo(), algorithm.min(hi(), p)); 215 } 216 217 /** 218 Returns an interval that has been expanded on each side by the given 219 distance "margin". If "margin" is negative, then shrink the interval on 220 each side by "margin" instead. The resulting interval may be empty. Any 221 expansion of an empty interval remains empty. 222 */ 223 R1Interval expanded(double margin) const { 224 if (isEmpty()) { 225 return this; 226 } 227 return R1Interval(lo() - margin, hi() + margin); 228 } 229 230 /// Returns the smallest interval that contains this interval and the given interval "y". 231 R1Interval unite(in R1Interval y) const { 232 if (isEmpty()) { 233 return y; 234 } else if (y.isEmpty()) { 235 return this; 236 } else { 237 return R1Interval(algorithm.min(lo(), y.lo()), algorithm.max(hi(), y.hi())); 238 } 239 } 240 241 /// Returns the intersection of this interval with the given interval. Empty intervals do not 242 /// need to be special-cased. 243 R1Interval intersection(in R1Interval y) const { 244 return R1Interval(algorithm.max(lo(), y.lo()), algorithm.min(hi(), y.hi())); 245 } 246 247 /// Supports the == and != operators. Return true if two intervals contain the same set of points. 248 bool opEquals(in R1Interval y) const { 249 return (lo() == y.lo() && hi() == y.hi()) || (isEmpty() && y.isEmpty()); 250 } 251 252 /** 253 Returns true if this interval can be transformed into the given interval 254 by moving each endpoint by at most "max_error". The empty interval is 255 considered to be positioned arbitrarily on the real line, thus any 256 interval with (length <= 2*max_error) matches the empty interval. 257 */ 258 bool approxEquals(in R1Interval y, double max_error = 1e-15) const { 259 if (isEmpty()) { 260 return y.getLength() <= 2 * max_error; 261 } 262 if (y.isEmpty()) { 263 return getLength() <= 2 * max_error; 264 } 265 return (math.fabs(y.lo() - lo()) <= max_error 266 && math.fabs(y.hi() - hi()) <= max_error); 267 } 268 269 string toString() const { 270 return "[" ~ conv.to!string(lo()) ~ ", " ~ conv.to!string(hi()) ~ "]"; 271 } 272 273 private: 274 // The default constructor creates an empty interval. (Any interval where 275 // lo > hi is considered to be empty.) 276 // 277 // Note: Don't construct an interval using the default constructor and 278 // set_lo()/set_hi(), since this technique doesn't work with S1Interval and 279 // is bad programming style anyways. If you need to set both endpoints, use 280 // the constructor below: 281 // 282 // lat_bounds_ = R1Interval(lat_lo, lat_hi); 283 Vector2_d _bounds = Vector2_d([1, 0]); 284 }