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 }