1 /******************************************************************************
2 * Copyright (c) 2014, Hobu Inc. (howard@hobu.co)
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following
8 * conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided
15 * with the distribution.
16 * * Neither the name of the Howard Butler or Hobu, Inc.
17 * the names of its contributors may be
18 * used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
32 * OF SUCH DAMAGE.
33 ****************************************************************************/
34
35
36 #include <cmath>
37 #include <algorithm>
38
39 #include "HexGrid.hpp"
40 #include "HexIter.hpp"
41 #include "Mathpair.hpp"
42 #include "Processor.hpp"
43 #include "Segment.hpp"
44
45 using namespace std;
46
47 namespace hexer
48 {
49
HexGrid(int dense_limit)50 HexGrid::HexGrid(int dense_limit) : m_height(-1.0), m_width(-1.0),
51 m_pos_roots(HexCompare()), m_dense_limit(dense_limit), m_miny(1)
52 {}
53
initialize(double height)54 void HexGrid::initialize(double height)
55 {
56 m_maxSample = 10000;
57 m_height = height;
58 m_miny = 1;
59 m_width = (3 / (2 * SQRT_3)) * m_height;
60 m_offsets[0] = Point(0, 0);
61 m_offsets[1] = Point(-m_width / 3, m_height / 2);
62 m_offsets[2] = Point(0, m_height);
63 m_offsets[3] = Point(2 * m_width / 3, m_height);
64 m_offsets[4] = Point(m_width, m_height / 2);
65 m_offsets[5] = Point(2 * m_width / 3, 0);
66 m_center_offset = Point(m_width / 3, m_height / 2);
67 }
68
dense(Hexagon * h)69 bool HexGrid::dense(Hexagon *h)
70 {
71 return h->count() >= m_dense_limit;
72 }
73
addPoint(Point p)74 void HexGrid::addPoint(Point p)
75 {
76 if (m_width < 0)
77 {
78 m_sample.push_back(p);
79 if (m_sample.size() >= m_maxSample)
80 processSample();
81 return;
82 }
83
84 Hexagon *h = findHexagon(p);
85 h->increment();
86 if (!h->dense())
87 {
88 if (dense(h))
89 {
90 h->setDense();
91 m_miny = std::min(m_miny, h->y() - 1);
92 if (h->possibleRoot())
93 m_pos_roots.insert(h);
94 markNeighborBelow(h);
95 }
96 }
97 }
98
processSample()99 void HexGrid::processSample()
100 {
101 if (m_width > 0 || m_sample.empty())
102 return;
103
104 double height = computeHexSize(m_sample, m_dense_limit);
105 initialize(height);
106 for (auto pi = m_sample.begin(); pi != m_sample.end(); ++pi)
107 addPoint(*pi);
108 m_sample.clear();
109 }
110
111 // A debugging function that can be used to make a particular hexagon
112 // dense.
addDenseHexagon(int x,int y)113 void HexGrid::addDenseHexagon(int x, int y)
114 {
115 Hexagon *h = getHexagon(x, y);
116 if (!h->dense())
117 {
118 h->setCount(m_dense_limit);
119 h->setDense();
120 m_miny = std::min(m_miny, h->y() - 1);
121 if (h->possibleRoot())
122 m_pos_roots.insert(h);
123 markNeighborBelow(h);
124 }
125 }
126
hexBegin()127 HexIter HexGrid::hexBegin()
128 {
129 return HexIter(m_hexes.begin(), this);
130 }
131
hexEnd()132 HexIter HexGrid::hexEnd()
133 {
134 return HexIter(m_hexes.end(), this);
135 }
136
markNeighborBelow(Hexagon * h)137 void HexGrid::markNeighborBelow(Hexagon *h)
138 {
139 Coord c = h->neighborCoord(3);
140 Hexagon *neighbor = getHexagon(c);
141 neighbor->setDenseNeighbor(0);
142 if (neighbor->dense() && !neighbor->possibleRoot())
143 m_pos_roots.erase(neighbor);
144 }
145
146 // first point (origin) and start of column 0
147 // |
148 // |
149 // | ------- column 0
150 // | |
151 // | | end of column 0 - start of column 1
152 // | | |
153 // | v | _____
154 // v____ v |\ |
155 // / \ | \ | Here's an expansion of what
156 // / 0,0 \____ | \ | I'm calling a mini-column,
157 // \ / \ | \ | showing two half rows.
158 // \____/ 1,0 \ |____\|
159 // / \ / | /| The top rectangle is the
160 // / 0,1 \____/ | / | negative slope case and
161 // \ / \ | / | the lower rectangle is the
162 // \____/ \ | / | positive slope case.
163 // |/____|
164 // ** <-- The area above these
165 // asterisks are the "mini-column"
166 // The mini-column is 1/3 the total width of the column.
167 //
168 // We are creating a tesselated plane of hexagons where one side of each
169 // hexagon is parallel with the X axis. We think of the columns of
170 // hexagons as all having the same X value. Hexagons lower than their
171 // neighbor have successive Y values.
172 //
173 // The hexagon in the second column but just below the hexagon in the first
174 // column as the same Y value as the hexagon above and to the left. The third
175 // column's Y values are the one less than the hexagon below and to the left
176 // as the second column.
177 //
178 // The first point, whatever it's X/Y location, is made the origin, and is
179 // placed at the top-left edge of hexagon 0,0.
180 //
findHexagon(Point p)181 Hexagon *HexGrid::findHexagon(Point p)
182 {
183 int x, y;
184
185 if (m_hexes.empty())
186 {
187 m_origin = p;
188 // Make a hex at the origin and insert it. Return a pointer
189 // to the hexagon in the map.
190 HexMap::value_type hexpair(Hexagon::key(0, 0), Hexagon(0, 0));
191 HexMap::iterator it = m_hexes.insert(hexpair).first;
192 return &it->second;
193 }
194
195 // Offset by the origin.
196 p -= m_origin;
197
198 double col = p.m_x / m_width;
199
200 // First calculate X and Y as if we had a bunch of offset rectangles.
201 // This works for 2/3 of the width of the hexagons.
202 x = (int)floor(col);
203 if (x % 2 == 0)
204 y = static_cast<int>(floor(p.m_y / m_height));
205 else
206 y = static_cast<int>(floor((p.m_y - (m_height / 2)) / m_height));
207
208 // Compute the column remainder to determine if we are in a strip where
209 // the hexagons overlap (the mini-column).
210 double xcolOffset = col - floor(col);
211 if (xcolOffset > 2.0/3.0)
212 {
213 // Calculate the xvalue as a fraction of the width of the column-piece
214 // containing multiple hex columns. These overlap columns are 1/3
215 // the total width of any column.
216
217 // Subtract the 2/3 of the value not relevant to the mini-column.
218 xcolOffset -= 2.0/3.0;
219 // Scale the value to the width of the mini-column.
220 xcolOffset *= 3.0;
221
222 // Each halfrow contains a single sloping edge of a hexagon.
223 // The slope of the edge is either sqrt(3) or -sqrt(3). The edge
224 // extends from top left to lower right or from bottom left to top
225 // right. What we do here is compute the horizontal fraction of
226 // the box (xcolOffset) and the vertical fraction of the box
227 // (yrowOffset) and then compare them.
228 double halfrow = p.m_y / (m_height / 2);
229 int halfy = (int)halfrow;
230 double yrowOffset = halfrow - floor(halfrow);
231
232 // Negative slope case.
233 if ((halfy % 2 == 0 && x % 2 == 0) || (x % 2 && halfy % 2))
234 {
235 if (xcolOffset > yrowOffset)
236 {
237 if (x % 2 == 0)
238 y--;
239 x++;
240 }
241 }
242 // Positive slope case.
243 else
244 {
245 if (yrowOffset > xcolOffset)
246 {
247 if (x % 2)
248 y++;
249 x++;
250 }
251 }
252 }
253 return getHexagon(x, y);
254 }
255
256 // Get the hexagon at position x, y. If it doesn't exist, create it.
257 // Never returns NULL.
getHexagon(int x,int y)258 Hexagon *HexGrid::getHexagon(int x, int y)
259 {
260 // Stick a hexagon into the map if necessary.
261 HexMap::value_type hexpair(Hexagon::key(x, y), Hexagon(x, y));
262 std::pair<HexMap::iterator,bool> retval;
263 retval = m_hexes.insert(hexpair);
264 HexMap::iterator it = retval.first;
265
266 Hexagon *hex_p = &(it->second);
267
268 // Return a pointer to the located hexagon.
269 return hex_p;
270 }
271
272 /**
273 // Walk the outside of the hexagons to make a path. Hexagon sides are labeled:
274 //
275 // __0_
276 // 1 / \ 5
277 // / \
278 // \ /
279 // 2 \____/ 4
280 // 3
281 **/
findShapes()282 void HexGrid::findShapes()
283 {
284 if (m_pos_roots.empty())
285 {
286 throw hexer_error("No areas of sufficient density - no shapes. "
287 "Decrease density or area size.");
288 }
289
290 while (m_pos_roots.size())
291 {
292 Hexagon *h = *m_pos_roots.begin();
293 findShape(h);
294 }
295 }
296
findParentPaths()297 void HexGrid::findParentPaths()
298 {
299 std::vector<Path *> roots;
300 for (size_t i = 0; i < m_paths.size(); ++i)
301 {
302 Path *p = m_paths[i];
303 findParentPath(p);
304 // Either add the path to the root list or the parent's list of
305 // children.
306 !p->parent() ? roots.push_back(p) : p->parent()->addChild(p);
307 }
308 for (size_t i = 0; i < roots.size(); ++i)
309 roots[i]->finalize(CLOCKWISE);
310
311 // In the end, the list of paths is just the root paths. Children can
312 // be retrieved from their parents.
313 m_paths = roots;
314 }
315
findParentPath(Path * p)316 void HexGrid::findParentPath(Path *p)
317 {
318 Segment s = p->rootSegment();
319 Hexagon *h = s.hex();
320 int y = h->y();
321 while (y >= m_miny)
322 {
323 HexPathMap::iterator it = m_hex_paths.find(h);
324 if (it != m_hex_paths.end())
325 {
326 Path *parentPath = it->second;
327 if (parentPath == p->parent())
328 {
329 p->setParent(NULL);
330 }
331 else if (!p->parent() && parentPath != p)
332 {
333 p->setParent(parentPath);
334 }
335 }
336 h = getHexagon(h->x(), --y);
337 }
338 }
339
findShape(Hexagon * hex)340 void HexGrid::findShape(Hexagon *hex)
341 {
342 if (!hex)
343 throw hexer_error("hexagon was null!");
344
345 Path *p = new Path(this, CLOCKWISE);
346 Segment first(hex, 0);
347 Segment cur(first);
348 do {
349 cleanPossibleRoot(cur, p);
350 p->push_back(cur);
351 Segment next = cur.leftClockwise(this);
352 if (!next.hex()->dense())
353 next = cur.rightClockwise(this);
354 cur = next;
355 } while (cur != first);
356 m_paths.push_back(p);
357 }
358
cleanPossibleRoot(Segment s,Path * p)359 void HexGrid::cleanPossibleRoot(Segment s, Path *p)
360 {
361 if (s.possibleRoot(this))
362 m_pos_roots.erase(s.hex());
363 if (s.horizontal())
364 {
365 s.normalize(this);
366 HexPathMap::value_type hexpath(s.hex(), p);
367 m_hex_paths.insert(hexpath);
368 }
369 }
370
toWKT(std::ostream & out) const371 void HexGrid::toWKT(std::ostream& out) const
372 {
373 auto writePath = [this, &out](size_t pathNum)
374 {
375 rootPaths()[pathNum]->toWKT(out);
376 };
377
378 out << "MULTIPOLYGON (";
379
380 if (rootPaths().size())
381 writePath(0);
382 for (size_t pi = 1; pi < rootPaths().size(); ++pi)
383 {
384 out << ",";
385 writePath(pi);
386 }
387 out << ")";
388 }
389
densePointCount() const390 size_t HexGrid::densePointCount() const
391 {
392 size_t count = 0;
393 for (auto it = m_hexes.begin(); it != m_hexes.end(); ++it)
394 {
395 const Hexagon& h = it->second;
396 if (h.dense())
397 count += h.count();
398 }
399 return count;
400 }
401
402 } //namespace hexer
403