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 }