1 // Copyright 2012 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 // Original author: ericv@google.com (Eric Veach) 16 // Converted to D: madric@gmail.com (Vijay Nayar) 17 18 module s2.r2rect; 19 20 import s2.r1interval; 21 import s2.r2point; 22 import s2.logger; 23 import conv = std.conv; 24 25 /** 26 * An R2Rect represents a closed axis-aligned rectangle in the (x,y) plane. 27 * 28 * This class is intended to be copied by value as desired. It uses 29 * the default copy constructor and assignment operator, however it is 30 * not a "plain old datatype" (POD) because it has virtual functions. 31 */ 32 struct R2Rect { 33 private: 34 R1Interval[2] _bounds; 35 36 public: 37 // Construct a rectangle from the given lower-left and upper-right points. 38 this(in R2Point lo, in R2Point hi) 39 out { 40 assert(isValid()); 41 } do { 42 _bounds[0] = R1Interval(lo.x(), hi.x()); 43 _bounds[1] = R1Interval(lo.y(), hi.y()); 44 } 45 46 // Construct a rectangle from the given intervals in x and y. The two 47 // intervals must either be both empty or both non-empty. 48 this(in R1Interval x, in R1Interval y) 49 out { 50 if (!isValid()) logger.logError("Invalid: " ~ toString()); 51 } do { 52 _bounds[0] = x; 53 _bounds[1] = y; 54 } 55 56 // The canonical empty rectangle. Use isEmpty() to test for empty 57 // rectangles, since they have more than one representation. 58 static R2Rect empty() { 59 return R2Rect(R1Interval.empty(), R1Interval.empty()); 60 } 61 62 // Construct a rectangle from a center point and size in each dimension. 63 // Both components of size should be non-negative, i.e. this method cannot 64 // be used to create an empty rectangle. 65 static R2Rect fromCenterSize(in R2Point center, in R2Point size) { 66 return R2Rect( 67 R1Interval(center.x() - 0.5 * size.x(), center.x() + 0.5 * size.x()), 68 R1Interval(center.y() - 0.5 * size.y(), center.y() + 0.5 * size.y())); 69 } 70 71 // Convenience method to construct a rectangle containing a single point. 72 static R2Rect fromPoint(in R2Point p) { 73 return R2Rect(p, p); 74 } 75 76 // Convenience method to construct the minimal bounding rectangle containing 77 // the two given points. This is equivalent to starting with an empty 78 // rectangle and calling AddPoint() twice. Note that it is different than 79 // the R2Rect(lo, hi) constructor, where the first point is always 80 // used as the lower-left corner of the resulting rectangle. 81 static R2Rect fromPointPair(in R2Point p1, in R2Point p2) { 82 return R2Rect( 83 R1Interval.fromPointPair(p1.x(), p2.x()), 84 R1Interval.fromPointPair(p1.y(), p2.y())); 85 } 86 87 // Accessor methods. 88 @property 89 R1Interval x() const { 90 return _bounds[0]; 91 } 92 93 @property 94 R1Interval y() const { 95 return _bounds[1]; 96 } 97 98 @property 99 R2Point lo() const { 100 return R2Point(x().lo(), y().lo()); 101 } 102 103 @property 104 R2Point hi() const { 105 return R2Point(x().hi(), y().hi()); 106 } 107 108 // Methods that allow the R2Rect to be accessed as a vector. 109 ref inout(R1Interval) opIndex(size_t i) inout { 110 return _bounds[i]; 111 } 112 113 void opIndexAssign(in R1Interval value, size_t i) 114 in { 115 assert(i >= 0 && i < 2); 116 } do { 117 _bounds[i] = value; 118 } 119 120 // Return true if the rectangle is valid, which essentially just means 121 // that if the bound for either axis is empty then both must be. 122 bool isValid() const { 123 // The x/y ranges must either be both empty or both non-empty. 124 return x().isEmpty() == y().isEmpty(); 125 } 126 127 // Return true if the rectangle is empty, i.e. it contains no points at all. 128 bool isEmpty() const { 129 return x().isEmpty(); 130 } 131 132 // Return the k-th vertex of the rectangle (k = 0,1,2,3) in CCW order. 133 // Vertex 0 is in the lower-left corner. For convenience, the argument is 134 // reduced modulo 4 to the range [0..3]. 135 R2Point getVertex(int k) const { 136 // Twiddle bits to return the points in CCW order (lower left, lower right, 137 // upper right, upper left). 138 int j = (k >> 1) & 1; 139 return getVertex(j ^ (k & 1), j); 140 } 141 142 // Return the vertex in direction "i" along the x-axis (0=left, 1=right) and 143 // direction "j" along the y-axis (0=down, 1=up). Equivalently, return the 144 // vertex constructed by selecting endpoint "i" of the x-interval (0=lo, 145 // 1=hi) and vertex "j" of the y-interval. 146 R2Point getVertex(int i, int j) const { 147 return R2Point(_bounds[0][i], _bounds[1][j]); 148 } 149 150 // Return the center of the rectangle in (x,y)-space. 151 R2Point getCenter() const { 152 return R2Point(x().getCenter(), y().getCenter()); 153 } 154 155 // Return the width and height of this rectangle in (x,y)-space. Empty 156 // rectangles have a negative width and height. 157 R2Point getSize() const { 158 return R2Point(x().getLength(), y().getLength()); 159 } 160 161 // Return true if the rectangle contains the given point. Note that 162 // rectangles are closed regions, i.e. they contain their boundary. 163 bool contains(in R2Point p) const { 164 return x().contains(p.x()) && y().contains(p.y()); 165 } 166 167 // Return true if and only if the given point is contained in the interior 168 // of the region (i.e. the region excluding its boundary). 169 bool interiorContains(in R2Point p) const { 170 return x().interiorContains(p.x()) && y().interiorContains(p.y()); 171 } 172 173 // Return true if and only if the rectangle contains the given other 174 // rectangle. 175 bool contains(in R2Rect other) const { 176 return x().contains(other.x()) && y().contains(other.y()); 177 } 178 179 // Return true if and only if the interior of this rectangle contains all 180 // points of the given other rectangle (including its boundary). 181 bool interiorContains(in R2Rect other) const { 182 return x().interiorContains(other.x()) && y().interiorContains(other.y()); 183 } 184 185 // Return true if this rectangle and the given other rectangle have any 186 // points in common. 187 bool intersects(in R2Rect other) const { 188 return x().intersects(other.x()) && y().intersects(other.y()); 189 } 190 191 // Return true if and only if the interior of this rectangle intersects 192 // any point (including the boundary) of the given other rectangle. 193 bool InteriorIntersects(in R2Rect other) const { 194 return x().interiorIntersects(other.x()) && y().interiorIntersects(other.y()); 195 } 196 197 // Expand the rectangle to include the given point. The rectangle is 198 // expanded by the minimum amount possible. 199 void addPoint(in R2Point p) { 200 _bounds[0].addPoint(p[0]); 201 _bounds[1].addPoint(p[1]); 202 } 203 204 // Expand the rectangle to include the given other rectangle. This is the 205 // same as replacing the rectangle by the union of the two rectangles, but 206 // is somewhat more efficient. 207 void addRect(in R2Rect other) { 208 _bounds[0].addInterval(other[0]); 209 _bounds[1].addInterval(other[1]); 210 } 211 212 // Return the closest point in the rectangle to the given point "p". 213 // The rectangle must be non-empty. 214 R2Point Project(in R2Point p) const { 215 return R2Point(x().project(p.x()), y().project(p.y())); 216 } 217 218 219 // Return a rectangle that has been expanded on each side in the x-direction 220 // by margin.x(), and on each side in the y-direction by margin.y(). If 221 // either margin is empty, then shrink the interval on the corresponding 222 // sides instead. The resulting rectangle may be empty. Any expansion of 223 // an empty rectangle remains empty. 224 R2Rect expanded(in R2Point margin) const { 225 R1Interval xx = x().expanded(margin.x()); 226 R1Interval yy = y().expanded(margin.y()); 227 if (xx.isEmpty() || yy.isEmpty()) return empty(); 228 return R2Rect(xx, yy); 229 } 230 231 R2Rect expanded(double margin) const { 232 return expanded(R2Point(margin, margin)); 233 } 234 235 // Return the smallest rectangle containing the union of this rectangle and 236 // the given rectangle. 237 R2Rect unite(in R2Rect other) const { 238 return R2Rect(x().unite(other.x()), y().unite(other.y())); 239 } 240 241 // Return the smallest rectangle containing the intersection of this 242 // rectangle and the given rectangle. 243 R2Rect intersection(in R2Rect other) const { 244 R1Interval xx = x().intersection(other.x()); 245 R1Interval yy = y().intersection(other.y()); 246 if (xx.isEmpty() || yy.isEmpty()) return empty(); 247 return R2Rect(xx, yy); 248 } 249 250 // Return true if two rectangles contains the same set of points. 251 bool opEquals(in R2Rect other) const { 252 return x() == other.x() && y() == other.y(); 253 } 254 255 // Return true if the x- and y-intervals of the two rectangles are the same 256 // up to the given tolerance (see r1interval.h for details). 257 bool approxEquals(in R2Rect other, double max_error = 1e-15) const { 258 return x().approxEquals(other.x(), max_error) 259 && y().approxEquals(other.y(), max_error); 260 } 261 262 string toString() const { 263 return "[Lo" ~ lo().toString() ~ ", Hi" ~ hi().toString() ~ "]"; 264 } 265 266 }