1 // Copyright 2013 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 
16 // Original author: ericv@google.com (Eric Veach)
17 // Converted to D:  madric@gmail.com (Vijay Nayar)
18 
19 module s2.s2padded_cell;
20 
21 import s2.r1interval;
22 import s2.r2rect;
23 import s2.s2cell_id;
24 import s2.s2coords : FaceSiTitoXYZ, IJ_TO_POS, INVERT_MASK, SiTitoST, STtoUV, SWAP_MASK,
25   UVtoST, POS_TO_IJ, POS_TO_ORIENTATION, STtoIJ;
26 import s2.s2point;
27 import std.algorithm : min, max;
28 import std.math : log2, floor;
29 
30 // S2PaddedCell represents an S2Cell whose (u,v)-range has been expanded on
31 // all sides by a given amount of "padding".  Unlike S2Cell, its methods and
32 // representation are optimized for clipping edges against S2Cell boundaries
33 // to determine which cells are intersected by a given set of edges.
34 //
35 // This class is intended to be copied by value as desired.
36 class S2PaddedCell {
37 public:
38   // Construct an S2PaddedCell for the given cell id and padding.
39   this(in S2CellId id, double padding) {
40     _id = id;
41     _padding = padding;
42 
43     if (_id.isFace()) {
44       // Fast path for constructing a top-level face (the most common case).
45       double limit = 1.0 + padding;
46       _bound = R2Rect(R1Interval(-limit, limit), R1Interval(-limit, limit));
47       _middle = R2Rect(R1Interval(-padding, padding), R1Interval(-padding, padding));
48       _ijLo[0] = _ijLo[1] = 0;
49       _orientation = _id.face() & 1;
50       _level = 0;
51     } else {
52       int[2] ij;
53       id.toFaceIJOrientation(ij[0], ij[1], _orientation);
54       _level = id.level();
55       _bound = S2CellId.IJLevelToBoundUV(ij, _level).expanded(padding);
56       int ij_size = S2CellId.getSizeIJ(_level);
57       _ijLo[0] = ij[0] & -ij_size;
58       _ijLo[1] = ij[1] & -ij_size;
59     }
60   }
61 
62   // Construct the child of "parent" with the given (i,j) index.  The four
63   // child cells have indices of (0,0), (0,1), (1,0), (1,1), where the i and j
64   // indices correspond to increasing u- and v-values respectively.
65   this(S2PaddedCell parent, int i, int j) {
66     _padding = parent._padding;
67     _bound = parent._bound;
68     _level = parent._level + 1;
69 
70     // Compute the position and orientation of the child incrementally from the
71     // orientation of the parent.
72     int pos = IJ_TO_POS[parent._orientation][2 * i + j];
73     _id = parent._id.child(pos);
74     int ij_size = S2CellId.getSizeIJ(_level);
75     _ijLo[0] = parent._ijLo[0] + i * ij_size;
76     _ijLo[1] = parent._ijLo[1] + j * ij_size;
77     _orientation = parent._orientation ^ POS_TO_ORIENTATION[pos];
78     // For each child, one corner of the bound is taken directly from the parent
79     // while the diagonally opposite corner is taken from middle().
80     R2Rect middle = parent.middle();
81     _bound[0][1 - i] = middle[0][1 - i];
82     _bound[1][1 - j] = middle[1][1 - j];
83   }
84 
85   const(S2CellId) id() const {
86     return _id;
87   }
88 
89   double padding() const {
90     return _padding;
91   }
92 
93   int level() const {
94     return _level;
95   }
96 
97   // Return the bound for this cell (including padding).
98   const(R2Rect) bound() const {
99     return _bound;
100   }
101 
102   // Return the "middle" of the padded cell, defined as the rectangle that
103   // belongs to all four children.
104   //
105   // Note that this method is *not* thread-safe, because the return value is
106   // computed on demand and cached.  (It is expected that this class will be
107   // mainly useful in the context of single-threaded recursive algorithms.)
108   const(R2Rect) middle() {
109     // We compute this field lazily because it is not needed the majority of the
110     // time (i.e., for cells where the recursion terminates).
111     if (_middle.isEmpty()) {
112       int ij_size = S2CellId.getSizeIJ(_level);
113       double u = STtoUV(SiTitoST(2 * _ijLo[0] + ij_size));
114       double v = STtoUV(SiTitoST(2 * _ijLo[1] + ij_size));
115       _middle = R2Rect(R1Interval(u - _padding, u + _padding),
116           R1Interval(v - _padding, v + _padding));
117     }
118     return _middle;
119   }
120 
121   // Return the (i,j) coordinates for the child cell at the given traversal
122   // position.  The traversal position corresponds to the order in which child
123   // cells are visited by the Hilbert curve.
124   void getChildIJ(int pos, out int i, out int j) const {
125     int ij = POS_TO_IJ[_orientation][pos];
126     i = ij >> 1;
127     j = ij & 1;
128   }
129 
130 
131   // Return the smallest cell that contains all descendants of this cell whose
132   // bounds intersect "rect".  For algorithms that use recursive subdivision
133   // to find the cells that intersect a particular object, this method can be
134   // used to skip all the initial subdivision steps where only one child needs
135   // to be expanded.
136   //
137   // Note that this method is not the same as returning the smallest cell that
138   // contains the intersection of this cell with "rect".  Because of the
139   // padding, even if one child completely contains "rect" it is still
140   // possible that a neighboring child also intersects "rect".
141   //
142   // REQUIRES: bound().Intersects(rect)
143   S2CellId shrinkToFit(in R2Rect rect) const
144   in {
145     assert(bound().intersects(rect));
146   } do {
147     // Quick rejection test: if "rect" contains the center of this cell along
148     // either axis, then no further shrinking is possible.
149     int ij_size = S2CellId.getSizeIJ(_level);
150     if (_level == 0) {
151       // Fast path (most calls to this function start with a face cell).
152       if (rect[0].contains(0) || rect[1].contains(0)) return id();
153     } else {
154       if (rect[0].contains(STtoUV(SiTitoST(2 * _ijLo[0] + ij_size))) ||
155           rect[1].contains(STtoUV(SiTitoST(2 * _ijLo[1] + ij_size)))) {
156         return id();
157       }
158     }
159     // Otherwise we expand "rect" by the given padding() on all sides and find
160     // the range of coordinates that it spans along the i- and j-axes.  We then
161     // compute the highest bit position at which the min and max coordinates
162     // differ.  This corresponds to the first cell level at which at least two
163     // children intersect "rect".
164 
165     // Increase the padding to compensate for the error in S2::UVtoST().
166     // (The constant below is a provable upper bound on the additional error.)
167     R2Rect padded = rect.expanded(padding() + 1.5 * double.epsilon);
168     int[2] ij_min;  // Min i- or j- coordinate spanned by "padded"
169     int[2] ij_xor;  // XOR of the min and max i- or j-coordinates
170     for (int d = 0; d < 2; ++d) {
171       ij_min[d] = max(_ijLo[d], STtoIJ(UVtoST(padded[d][0])));
172       int ij_max = min(_ijLo[d] + ij_size - 1, STtoIJ(UVtoST(padded[d][1])));
173       ij_xor[d] = ij_min[d] ^ ij_max;
174     }
175     // Compute the highest bit position where the two i- or j-endpoints differ,
176     // and then choose the cell level that includes both of these endpoints.  So
177     // if both pairs of endpoints are equal we choose kMaxLevel; if they differ
178     // only at bit 0, we choose (kMaxLevel - 1), and so on.
179     int level_msb = ((ij_xor[0] | ij_xor[1]) << 1) + 1;
180     int level = S2CellId.MAX_LEVEL - cast(int) floor(log2(level_msb));
181     if (level <= _level) return id();
182     return S2CellId.fromFaceIJ(id().face(), ij_min[0], ij_min[1]).parent(level);
183   }
184 
185   // Return the center of this cell.
186   S2Point getCenter() const {
187     int ij_size = S2CellId.getSizeIJ(_level);
188     uint si = 2 * _ijLo[0] + ij_size;
189     uint ti = 2 * _ijLo[1] + ij_size;
190     return FaceSiTitoXYZ(_id.face(), si, ti).normalize();
191   }
192 
193   // Return the vertex where the S2 space-filling curve enters this cell.
194   S2Point getEntryVertex() const {
195     // The curve enters at the (0,0) vertex unless the axis directions are
196     // reversed, in which case it enters at the (1,1) vertex.
197     uint i = _ijLo[0];
198     uint j = _ijLo[1];
199     if (_orientation & INVERT_MASK) {
200       int ij_size = S2CellId.getSizeIJ(_level);
201       i += ij_size;
202       j += ij_size;
203     }
204     return FaceSiTitoXYZ(_id.face(), 2 * i, 2 * j).normalize();
205   }
206 
207   // Return the vertex where the S2 space-filling curve exits this cell.
208   S2Point getExitVertex() const {
209     // The curve exits at the (1,0) vertex unless the axes are swapped or
210     // inverted but not both, in which case it exits at the (0,1) vertex.
211     uint i = _ijLo[0];
212     uint j = _ijLo[1];
213     int ij_size = S2CellId.getSizeIJ(_level);
214     if (_orientation == 0 || _orientation == SWAP_MASK + INVERT_MASK) {
215       i += ij_size;
216     } else {
217       j += ij_size;
218     }
219     return FaceSiTitoXYZ(_id.face(), 2 * i, 2 * j).normalize();
220   }
221 
222   override
223   string toString() const {
224     import std.conv;
225     return "S2PaddedCell(id=" ~ _id.toString() ~ ", padding=" ~ _padding.to!string
226         ~ ", bound=" ~ _bound.toString() ~ ")";
227   }
228 
229  private:
230   const(S2CellId) _id;
231   double _padding;
232   R2Rect _bound;     // Bound in (u,v)-space
233 
234   // The rectangle in (u,v)-space that belongs to all four padded children.
235   // It is computed on demand by the middle() accessor method.
236   R2Rect _middle;
237 
238   int[2] _ijLo;     // Minimum (i,j)-coordinates of this cell, before padding
239   int _orientation;  // Hilbert curve orientation of this cell (see s2coords.h)
240   int _level;        // Level of this cell (see s2coords.h)
241 }