1 #ifndef BOTTOMLEFT_HPP
2 #define BOTTOMLEFT_HPP
3 
4 #include <limits>
5 
6 #include "placer_boilerplate.hpp"
7 
8 namespace libnest2d { namespace placers {
9 
10 template<class T, class = T> struct DefaultEpsilon {};
11 
12 template<class T>
13 struct DefaultEpsilon<T, enable_if_t<std::is_integral<T>::value, T> > {
14     static const T Value = 1;
15 };
16 
17 template<class T>
18 struct DefaultEpsilon<T, enable_if_t<std::is_floating_point<T>::value, T> > {
19     static const T Value = 1e-3;
20 };
21 
22 template<class RawShape>
23 struct BLConfig {
24     DECLARE_MAIN_TYPES(RawShape);
25 
26     Coord min_obj_distance = 0;
27     Coord epsilon = DefaultEpsilon<Coord>::Value;
28     bool allow_rotations = false;
29 };
30 
31 template<class RawShape>
32 class _BottomLeftPlacer: public PlacerBoilerplate<
33         _BottomLeftPlacer<RawShape>,
34         RawShape, _Box<TPoint<RawShape>>,
35         BLConfig<RawShape> >
36 {
37     using Base = PlacerBoilerplate<_BottomLeftPlacer<RawShape>, RawShape,
38                                     _Box<TPoint<RawShape>>, BLConfig<RawShape>>;
39     DECLARE_PLACER(Base)
40 
41 public:
42 
_BottomLeftPlacer(const BinType & bin)43     explicit _BottomLeftPlacer(const BinType& bin): Base(bin) {}
44 
45     template<class Range = ConstItemRange<typename Base::DefaultIter>>
trypack(Item & item,const Range &=Range ())46     PackResult trypack(Item& item,
47                        const Range& = Range())
48     {
49         auto r = _trypack(item);
50         if(!r && Base::config_.allow_rotations) {
51 
52             item.rotate(Degrees(90));
53             r =_trypack(item);
54         }
55         return r;
56     }
57 
58     enum class Dir {
59         LEFT,
60         DOWN
61     };
62 
leftPoly(const Item & item) const63     inline RawShape leftPoly(const Item& item) const {
64         return toWallPoly(item, Dir::LEFT);
65     }
66 
downPoly(const Item & item) const67     inline RawShape downPoly(const Item& item) const {
68         return toWallPoly(item, Dir::DOWN);
69     }
70 
availableSpaceLeft(const Item & item)71     inline Coord availableSpaceLeft(const Item& item) {
72         return availableSpace(item, Dir::LEFT);
73     }
74 
availableSpaceDown(const Item & item)75     inline Coord availableSpaceDown(const Item& item) {
76         return availableSpace(item, Dir::DOWN);
77     }
78 
79 protected:
80 
_trypack(Item & item)81     PackResult _trypack(Item& item) {
82 
83         // Get initial position for item in the top right corner
84         setInitialPosition(item);
85 
86         Coord d = availableSpaceDown(item);
87         auto eps = config_.epsilon;
88         bool can_move = d > eps;
89         bool can_be_packed = can_move;
90         bool left = true;
91 
92         while(can_move) {
93             if(left) { // write previous down move and go down
94                 item.translate({0, -d+eps});
95                 d = availableSpaceLeft(item);
96                 can_move = d > eps;
97                 left = false;
98             } else { // write previous left move and go down
99                 item.translate({-d+eps, 0});
100                 d = availableSpaceDown(item);
101                 can_move = d > eps;
102                 left = true;
103             }
104         }
105 
106         if(can_be_packed) {
107             Item trsh(item.transformedShape());
108             for(auto& v : trsh) can_be_packed = can_be_packed &&
109                     getX(v) < bin_.width() &&
110                     getY(v) < bin_.height();
111         }
112 
113         return can_be_packed? PackResult(item) : PackResult();
114     }
115 
setInitialPosition(Item & item)116     void setInitialPosition(Item& item) {
117         auto bb = item.boundingBox();
118 
119         Vertex v = { getX(bb.maxCorner()), getY(bb.minCorner()) };
120 
121 
122         Coord dx = getX(bin_.maxCorner()) - getX(v);
123         Coord dy = getY(bin_.maxCorner()) - getY(v);
124 
125         item.translate({dx, dy});
126     }
127 
128     template<class C = Coord>
129     static enable_if_t<std::is_floating_point<C>::value, bool>
isInTheWayOf(const Item & item,const Item & other,const RawShape & scanpoly)130     isInTheWayOf( const Item& item,
131                   const Item& other,
132                   const RawShape& scanpoly)
133     {
134         auto tsh = other.transformedShape();
135         return ( sl::intersects(tsh, scanpoly) ||
136                  sl::isInside(tsh, scanpoly) ) &&
137                ( !sl::intersects(tsh, item.rawShape()) &&
138                  !sl::isInside(tsh, item.rawShape()) );
139     }
140 
141     template<class C = Coord>
142     static enable_if_t<std::is_integral<C>::value, bool>
isInTheWayOf(const Item & item,const Item & other,const RawShape & scanpoly)143     isInTheWayOf( const Item& item,
144                   const Item& other,
145                   const RawShape& scanpoly)
146     {
147         auto tsh = other.transformedShape();
148 
149         bool inters_scanpoly = sl::intersects(tsh, scanpoly) &&
150                 !sl::touches(tsh, scanpoly);
151         bool inters_item = sl::intersects(tsh, item.rawShape()) &&
152                 !sl::touches(tsh, item.rawShape());
153 
154         return ( inters_scanpoly ||
155                  sl::isInside(tsh, scanpoly)) &&
156                ( !inters_item &&
157                  !sl::isInside(tsh, item.rawShape())
158                  );
159     }
160 
itemsInTheWayOf(const Item & item,const Dir dir)161     ItemGroup itemsInTheWayOf(const Item& item, const Dir dir) {
162         // Get the left or down polygon, that has the same area as the shadow
163         // of input item reflected to the left or downwards
164         auto&& scanpoly = dir == Dir::LEFT? leftPoly(item) :
165                                             downPoly(item);
166 
167         ItemGroup ret;    // packed items 'in the way' of item
168         ret.reserve(items_.size());
169 
170         // Predicate to find items that are 'in the way' for left (down) move
171         auto predicate = [&scanpoly, &item](const Item& it) {
172             return isInTheWayOf(item, it, scanpoly);
173         };
174 
175         // Get the items that are in the way for the left (or down) movement
176         std::copy_if(items_.begin(), items_.end(),
177                      std::back_inserter(ret), predicate);
178 
179         return ret;
180     }
181 
availableSpace(const Item & _item,const Dir dir)182     Coord availableSpace(const Item& _item, const Dir dir) {
183 
184         Item item (_item.transformedShape());
185 
186 
187         std::function<Coord(const Vertex&)> getCoord;
188         std::function< std::pair<Coord, bool>(const Segment&, const Vertex&) >
189             availableDistanceSV;
190 
191         std::function< std::pair<Coord, bool>(const Vertex&, const Segment&) >
192             availableDistance;
193 
194         if(dir == Dir::LEFT) {
195             getCoord = [](const Vertex& v) { return getX(v); };
196             availableDistance = pointlike::horizontalDistance<Vertex>;
197             availableDistanceSV = [](const Segment& s, const Vertex& v) {
198                 auto ret = pointlike::horizontalDistance<Vertex>(v, s);
199                 if(ret.second) ret.first = -ret.first;
200                 return ret;
201             };
202         }
203         else {
204             getCoord = [](const Vertex& v) { return getY(v); };
205             availableDistance = pointlike::verticalDistance<Vertex>;
206             availableDistanceSV = [](const Segment& s, const Vertex& v) {
207                 auto ret = pointlike::verticalDistance<Vertex>(v, s);
208                 if(ret.second) ret.first = -ret.first;
209                 return ret;
210             };
211         }
212 
213         auto&& items_in_the_way = itemsInTheWayOf(item, dir);
214 
215         // Comparison function for finding min vertex
216         auto cmp = [&getCoord](const Vertex& v1, const Vertex& v2) {
217             return getCoord(v1) < getCoord(v2);
218         };
219 
220         // find minimum left or down coordinate of item
221         auto minvertex_it = std::min_element(item.begin(),
222                                                    item.end(),
223                                                    cmp);
224 
225         // Get the initial distance in floating point
226         Coord m = getCoord(*minvertex_it);
227 
228         // Check available distance for every vertex of item to the objects
229         // in the way for the nearest intersection
230         if(!items_in_the_way.empty()) { // This is crazy, should be optimized...
231             for(Item& pleft : items_in_the_way) {
232                 // For all segments in items_to_left
233 
234                 assert(pleft.vertexCount() > 0);
235 
236                 auto trpleft_poly = pleft.transformedShape();
237                 auto& trpleft = sl::contour(trpleft_poly);
238                 auto first = sl::begin(trpleft);
239                 auto next = first + 1;
240                 auto endit = sl::end(trpleft);
241 
242                 while(next != endit) {
243                     Segment seg(*(first++), *(next++));
244                     for(auto& v : item) {   // For all vertices in item
245 
246                         auto d = availableDistance(v, seg);
247 
248                         if(d.second && d.first < m)  m = d.first;
249                     }
250                 }
251             }
252 
253             auto first = item.begin();
254             auto next = first + 1;
255             auto endit = item.end();
256 
257             // For all edges in item:
258             while(next != endit) {
259                 Segment seg(*(first++), *(next++));
260 
261                 // for all shapes in items_to_left
262                 for(Item& sh : items_in_the_way) {
263                     assert(sh.vertexCount() > 0);
264 
265                     Item tsh(sh.transformedShape());
266                     for(auto& v : tsh) {   // For all vertices in item
267 
268                         auto d = availableDistanceSV(seg, v);
269 
270                         if(d.second && d.first < m)  m = d.first;
271                     }
272                 }
273             }
274         }
275 
276         return m;
277     }
278 
279     /**
280      * Implementation of the left (and down) polygon as described by
281      * [López-Camacho et al. 2013]\
282      * (http://www.cs.stir.ac.uk/~goc/papers/EffectiveHueristic2DAOR2013.pdf)
283      * see algorithm 8 for details...
284      */
toWallPoly(const Item & _item,const Dir dir) const285     RawShape toWallPoly(const Item& _item, const Dir dir) const {
286         // The variable names reflect the case of left polygon calculation.
287         //
288         // We will iterate through the item's vertices and search for the top
289         // and bottom vertices (or right and left if dir==Dir::DOWN).
290         // Save the relevant vertices and their indices into `bottom` and
291         // `top` vectors. In case of left polygon construction these will
292         // contain the top and bottom polygons which have the same vertical
293         // coordinates (in case there is more of them).
294         //
295         // We get the leftmost (or downmost) vertex from the `bottom` and `top`
296         // vectors and construct the final polygon.
297 
298         Item item (_item.transformedShape());
299 
300         auto getCoord = [dir](const Vertex& v) {
301             return dir == Dir::LEFT? getY(v) : getX(v);
302         };
303 
304         Coord max_y = std::numeric_limits<Coord>::min();
305         Coord min_y = std::numeric_limits<Coord>::max();
306 
307         using El = std::pair<size_t, std::reference_wrapper<const Vertex>>;
308 
309         std::function<bool(const El&, const El&)> cmp;
310 
311         if(dir == Dir::LEFT)
312             cmp = [](const El& e1, const El& e2) {
313                 return getX(e1.second.get()) < getX(e2.second.get());
314             };
315         else
316             cmp = [](const El& e1, const El& e2) {
317                 return getY(e1.second.get()) < getY(e2.second.get());
318             };
319 
320         std::vector< El > top;
321         std::vector< El > bottom;
322 
323         size_t idx = 0;
324         for(auto& v : item) { // Find the bottom and top vertices and save them
325             auto vref = std::cref(v);
326             auto vy = getCoord(v);
327 
328             if( vy > max_y ) {
329                 max_y = vy;
330                 top.clear();
331                 top.emplace_back(idx, vref);
332             }
333             else if(vy == max_y) { top.emplace_back(idx, vref); }
334 
335             if(vy < min_y) {
336                 min_y = vy;
337                 bottom.clear();
338                 bottom.emplace_back(idx, vref);
339             }
340             else if(vy == min_y) { bottom.emplace_back(idx, vref); }
341 
342             idx++;
343         }
344 
345         // Get the top and bottom leftmost vertices, or the right and left
346         // downmost vertices (if dir == Dir::DOWN)
347         auto topleft_it = std::min_element(top.begin(), top.end(), cmp);
348         auto bottomleft_it =
349                 std::min_element(bottom.begin(), bottom.end(), cmp);
350 
351         auto& topleft_vertex = topleft_it->second.get();
352         auto& bottomleft_vertex = bottomleft_it->second.get();
353 
354         // Start and finish positions for the vertices that will be part of the
355         // new polygon
356         auto start = std::min(topleft_it->first, bottomleft_it->first);
357         auto finish = std::max(topleft_it->first, bottomleft_it->first);
358 
359         RawShape ret;
360 
361         // the return shape
362         auto& rsh = sl::contour(ret);
363 
364         // reserve for all vertices plus 2 for the left horizontal wall, 2 for
365         // the additional vertices for maintaning min object distance
366         sl::reserve(rsh, finish-start+4);
367 
368         /*auto addOthers = [&rsh, finish, start, &item](){
369             for(size_t i = start+1; i < finish; i++)
370                 sl::addVertex(rsh, item.vertex(i));
371         };*/
372 
373         auto reverseAddOthers = [&rsh, finish, start, &item](){
374             for(auto i = finish-1; i > start; i--)
375                 sl::addVertex(rsh, item.vertex(
376                                          static_cast<unsigned long>(i)));
377         };
378 
379         // Final polygon construction...
380 
381         static_assert(OrientationType<RawShape>::Value ==
382                       Orientation::CLOCKWISE,
383                       "Counter clockwise toWallPoly() Unimplemented!");
384 
385         // Clockwise polygon construction
386 
387         sl::addVertex(rsh, topleft_vertex);
388 
389         if(dir == Dir::LEFT) reverseAddOthers();
390         else {
391             sl::addVertex(rsh, getX(topleft_vertex), 0);
392             sl::addVertex(rsh, getX(bottomleft_vertex), 0);
393         }
394 
395         sl::addVertex(rsh, bottomleft_vertex);
396 
397         if(dir == Dir::LEFT) {
398             sl::addVertex(rsh, 0, getY(bottomleft_vertex));
399             sl::addVertex(rsh, 0, getY(topleft_vertex));
400         }
401         else reverseAddOthers();
402 
403 
404         // Close the polygon
405         sl::addVertex(rsh, topleft_vertex);
406 
407         return ret;
408     }
409 
410 };
411 
412 }
413 }
414 
415 #endif //BOTTOMLEFT_HPP
416