1 #ifndef GEOMETRIES_NOFITPOLYGON_HPP
2 #define GEOMETRIES_NOFITPOLYGON_HPP
3
4 #include <algorithm>
5 #include <functional>
6 #include <vector>
7 #include <iterator>
8
9 #include <libnest2d/geometry_traits.hpp>
10
11 namespace libnest2d {
12
13 namespace __nfp {
14 // Do not specialize this...
15 template<class RawShape, class Unit = TCompute<RawShape>>
_vsort(const TPoint<RawShape> & v1,const TPoint<RawShape> & v2)16 inline bool _vsort(const TPoint<RawShape>& v1, const TPoint<RawShape>& v2)
17 {
18 Unit x1 = getX(v1), x2 = getX(v2), y1 = getY(v1), y2 = getY(v2);
19 return y1 == y2 ? x1 < x2 : y1 < y2;
20 }
21
22 template<class EdgeList, class RawShape, class Vertex = TPoint<RawShape>>
buildPolygon(const EdgeList & edgelist,RawShape & rpoly,Vertex & top_nfp)23 inline void buildPolygon(const EdgeList& edgelist,
24 RawShape& rpoly,
25 Vertex& top_nfp)
26 {
27 namespace sl = shapelike;
28
29 auto& rsh = sl::contour(rpoly);
30
31 sl::reserve(rsh, 2*edgelist.size());
32
33 // Add the two vertices from the first edge into the final polygon.
34 sl::addVertex(rsh, edgelist.front().first());
35 sl::addVertex(rsh, edgelist.front().second());
36
37 // Sorting function for the nfp reference vertex search
38 auto& cmp = _vsort<RawShape>;
39
40 // the reference (rightmost top) vertex so far
41 top_nfp = *std::max_element(sl::cbegin(rsh), sl::cend(rsh), cmp );
42
43 auto tmp = std::next(sl::begin(rsh));
44
45 // Construct final nfp by placing each edge to the end of the previous
46 for(auto eit = std::next(edgelist.begin());
47 eit != edgelist.end();
48 ++eit)
49 {
50 auto d = *tmp - eit->first();
51 Vertex p = eit->second() + d;
52
53 sl::addVertex(rsh, p);
54
55 // Set the new reference vertex
56 if(cmp(top_nfp, p)) top_nfp = p;
57
58 tmp = std::next(tmp);
59 }
60
61 }
62
63 template<class Container, class Iterator = typename Container::iterator>
advance(Iterator & it,Container & cont,bool direction)64 void advance(Iterator& it, Container& cont, bool direction)
65 {
66 int dir = direction ? 1 : -1;
67 if(dir < 0 && it == cont.begin()) it = std::prev(cont.end());
68 else it += dir;
69 if(dir > 0 && it == cont.end()) it = cont.begin();
70 }
71
72 }
73
74 /// A collection of static methods for handling the no fit polygon creation.
75 namespace nfp {
76
77 const double BP2D_CONSTEXPR TwoPi = 2*Pi;
78
79 /// The complexity level of a polygon that an NFP implementation can handle.
80 enum class NfpLevel: unsigned {
81 CONVEX_ONLY,
82 ONE_CONVEX,
83 BOTH_CONCAVE,
84 ONE_CONVEX_WITH_HOLES,
85 BOTH_CONCAVE_WITH_HOLES
86 };
87
88 template<class RawShape>
89 using NfpResult = std::pair<RawShape, TPoint<RawShape>>;
90
91 template<class RawShape> struct MaxNfpLevel {
92 static const BP2D_CONSTEXPR NfpLevel value = NfpLevel::CONVEX_ONLY;
93 };
94
95
96 // Shorthand for a pile of polygons
97 template<class RawShape>
98 using Shapes = TMultiShape<RawShape>;
99
100 /**
101 * Merge a bunch of polygons with the specified additional polygon.
102 *
103 * \tparam RawShape the Polygon data type.
104 * \param shc The pile of polygons that will be unified with sh.
105 * \param sh A single polygon to unify with shc.
106 *
107 * \return A set of polygons that is the union of the input polygons. Note that
108 * mostly it will be a set containing only one big polygon but if the input
109 * polygons are disjunct than the resulting set will contain more polygons.
110 */
111 template<class RawShapes>
merge(const RawShapes &)112 inline RawShapes merge(const RawShapes& /*shc*/)
113 {
114 static_assert(always_false<RawShapes>::value,
115 "Nfp::merge(shapes, shape) unimplemented!");
116 }
117
118 /**
119 * Merge a bunch of polygons with the specified additional polygon.
120 *
121 * \tparam RawShape the Polygon data type.
122 * \param shc The pile of polygons that will be unified with sh.
123 * \param sh A single polygon to unify with shc.
124 *
125 * \return A set of polygons that is the union of the input polygons. Note that
126 * mostly it will be a set containing only one big polygon but if the input
127 * polygons are disjunct than the resulting set will contain more polygons.
128 */
129 template<class RawShape>
merge(const TMultiShape<RawShape> & shc,const RawShape & sh)130 inline TMultiShape<RawShape> merge(const TMultiShape<RawShape>& shc,
131 const RawShape& sh)
132 {
133 auto m = nfp::merge(shc);
134 m.emplace_back(sh);
135 return nfp::merge(m);
136 }
137
138 /**
139 * Get the vertex of the polygon that is at the lowest values (bottom) in the Y
140 * axis and if there are more than one vertices on the same Y coordinate than
141 * the result will be the leftmost (with the highest X coordinate).
142 */
143 template<class RawShape>
leftmostDownVertex(const RawShape & sh)144 inline TPoint<RawShape> leftmostDownVertex(const RawShape& sh)
145 {
146
147 // find min x and min y vertex
148 auto it = std::min_element(shapelike::cbegin(sh), shapelike::cend(sh),
149 __nfp::_vsort<RawShape>);
150
151 return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;;
152 }
153
154 /**
155 * Get the vertex of the polygon that is at the highest values (top) in the Y
156 * axis and if there are more than one vertices on the same Y coordinate than
157 * the result will be the rightmost (with the lowest X coordinate).
158 */
159 template<class RawShape>
rightmostUpVertex(const RawShape & sh)160 TPoint<RawShape> rightmostUpVertex(const RawShape& sh)
161 {
162
163 // find max x and max y vertex
164 auto it = std::max_element(shapelike::cbegin(sh), shapelike::cend(sh),
165 __nfp::_vsort<RawShape>);
166
167 return it == shapelike::cend(sh) ? TPoint<RawShape>() : *it;
168 }
169
170 /**
171 * A method to get a vertex from a polygon that always maintains a relative
172 * position to the coordinate system: It is always the rightmost top vertex.
173 *
174 * This way it does not matter in what order the vertices are stored, the
175 * reference will be always the same for the same polygon.
176 */
177 template<class RawShape>
referenceVertex(const RawShape & sh)178 inline TPoint<RawShape> referenceVertex(const RawShape& sh)
179 {
180 return rightmostUpVertex(sh);
181 }
182
183 /**
184 * The "trivial" Cuninghame-Green implementation of NFP for convex polygons.
185 *
186 * You can use this even if you provide implementations for the more complex
187 * cases (Through specializing the the NfpImpl struct). Currently, no other
188 * cases are covered in the library.
189 *
190 * Complexity should be no more than nlogn (std::sort) in the number of edges
191 * of the input polygons.
192 *
193 * \tparam RawShape the Polygon data type.
194 * \param sh The stationary polygon
195 * \param cother The orbiting polygon
196 * \return Returns a pair of the NFP and its reference vertex of the two input
197 * polygons which have to be strictly convex. The resulting NFP is proven to be
198 * convex as well in this case.
199 *
200 */
201 template<class RawShape, class Ratio = double>
nfpConvexOnly(const RawShape & sh,const RawShape & other)202 inline NfpResult<RawShape> nfpConvexOnly(const RawShape& sh,
203 const RawShape& other)
204 {
205 using Vertex = TPoint<RawShape>; using Edge = _Segment<Vertex>;
206 namespace sl = shapelike;
207
208 RawShape rsh; // Final nfp placeholder
209 Vertex top_nfp;
210 std::vector<Edge> edgelist;
211
212 auto cap = sl::contourVertexCount(sh) + sl::contourVertexCount(other);
213
214 // Reserve the needed memory
215 edgelist.reserve(cap);
216 sl::reserve(rsh, static_cast<unsigned long>(cap));
217
218 { // place all edges from sh into edgelist
219 auto first = sl::cbegin(sh);
220 auto next = std::next(first);
221
222 while(next != sl::cend(sh)) {
223 edgelist.emplace_back(*(first), *(next));
224 ++first; ++next;
225 }
226 }
227
228 { // place all edges from other into edgelist
229 auto first = sl::cbegin(other);
230 auto next = std::next(first);
231
232 while(next != sl::cend(other)) {
233 edgelist.emplace_back(*(next), *(first));
234 ++first; ++next;
235 }
236 }
237
238 std::sort(edgelist.begin(), edgelist.end(),
239 [](const Edge& e1, const Edge& e2)
240 {
241 Vertex ax(1, 0); // Unit vector for the X axis
242
243 // get cectors from the edges
244 Vertex p1 = e1.second() - e1.first();
245 Vertex p2 = e2.second() - e2.first();
246
247 // Quadrant mapping array. The quadrant of a vector can be determined
248 // from the dot product of the vector and its perpendicular pair
249 // with the unit vector X axis. The products will carry the values
250 // lcos = dot(p, ax) = l * cos(phi) and
251 // lsin = -dotperp(p, ax) = l * sin(phi) where
252 // l is the length of vector p. From the signs of these values we can
253 // construct an index which has the sign of lcos as MSB and the
254 // sign of lsin as LSB. This index can be used to retrieve the actual
255 // quadrant where vector p resides using the following map:
256 // (+ is 0, - is 1)
257 // cos | sin | decimal | quadrant
258 // + | + | 0 | 0
259 // + | - | 1 | 3
260 // - | + | 2 | 1
261 // - | - | 3 | 2
262 std::array<int, 4> quadrants {0, 3, 1, 2 };
263
264 std::array<int, 2> q {0, 0}; // Quadrant indices for p1 and p2
265
266 using TDots = std::array<TCompute<Vertex>, 2>;
267 TDots lcos { pl::dot(p1, ax), pl::dot(p2, ax) };
268 TDots lsin { -pl::dotperp(p1, ax), -pl::dotperp(p2, ax) };
269
270 // Construct the quadrant indices for p1 and p2
271 for(size_t i = 0; i < 2; ++i)
272 if(lcos[i] == 0) q[i] = lsin[i] > 0 ? 1 : 3;
273 else if(lsin[i] == 0) q[i] = lcos[i] > 0 ? 0 : 2;
274 else q[i] = quadrants[((lcos[i] < 0) << 1) + (lsin[i] < 0)];
275
276 if(q[0] == q[1]) { // only bother if p1 and p2 are in the same quadrant
277 auto lsq1 = pl::magnsq(p1); // squared magnitudes, avoid sqrt
278 auto lsq2 = pl::magnsq(p2); // squared magnitudes, avoid sqrt
279
280 // We will actually compare l^2 * cos^2(phi) which saturates the
281 // cos function. But with the quadrant info we can get the sign back
282 int sign = q[0] == 1 || q[0] == 2 ? -1 : 1;
283
284 // If Ratio is an actual rational type, there is no precision loss
285 auto pcos1 = Ratio(lcos[0]) / lsq1 * sign * lcos[0];
286 auto pcos2 = Ratio(lcos[1]) / lsq2 * sign * lcos[1];
287
288 return q[0] < 2 ? pcos1 < pcos2 : pcos1 > pcos2;
289 }
290
291 // If in different quadrants, compare the quadrant indices only.
292 return q[0] > q[1];
293 });
294
295 __nfp::buildPolygon(edgelist, rsh, top_nfp);
296
297 return {rsh, top_nfp};
298 }
299
300 template<class RawShape>
nfpSimpleSimple(const RawShape & cstationary,const RawShape & cother)301 NfpResult<RawShape> nfpSimpleSimple(const RawShape& cstationary,
302 const RawShape& cother)
303 {
304
305 // Algorithms are from the original algorithm proposed in paper:
306 // https://eprints.soton.ac.uk/36850/1/CORMSIS-05-05.pdf
307
308 // /////////////////////////////////////////////////////////////////////////
309 // Algorithm 1: Obtaining the minkowski sum
310 // /////////////////////////////////////////////////////////////////////////
311
312 // I guess this is not a full minkowski sum of the two input polygons by
313 // definition. This yields a subset that is compatible with the next 2
314 // algorithms.
315
316 using Result = NfpResult<RawShape>;
317 using Vertex = TPoint<RawShape>;
318 using Coord = TCoord<Vertex>;
319 using Edge = _Segment<Vertex>;
320 namespace sl = shapelike;
321 using std::signbit;
322 using std::sort;
323 using std::vector;
324 using std::ref;
325 using std::reference_wrapper;
326
327 // TODO The original algorithms expects the stationary polygon in
328 // counter clockwise and the orbiter in clockwise order.
329 // So for preventing any further complication, I will make the input
330 // the way it should be, than make my way around the orientations.
331
332 // Reverse the stationary contour to counter clockwise
333 auto stcont = sl::contour(cstationary);
334 {
335 std::reverse(sl::begin(stcont), sl::end(stcont));
336 stcont.pop_back();
337 auto it = std::min_element(sl::begin(stcont), sl::end(stcont),
338 [](const Vertex& v1, const Vertex& v2) {
339 return getY(v1) < getY(v2);
340 });
341 std::rotate(sl::begin(stcont), it, sl::end(stcont));
342 sl::addVertex(stcont, sl::front(stcont));
343 }
344 RawShape stationary;
345 sl::contour(stationary) = stcont;
346
347 // Reverse the orbiter contour to counter clockwise
348 auto orbcont = sl::contour(cother);
349 {
350 std::reverse(orbcont.begin(), orbcont.end());
351
352 // Step 1: Make the orbiter reverse oriented
353
354 orbcont.pop_back();
355 auto it = std::min_element(orbcont.begin(), orbcont.end(),
356 [](const Vertex& v1, const Vertex& v2) {
357 return getY(v1) < getY(v2);
358 });
359
360 std::rotate(orbcont.begin(), it, orbcont.end());
361 orbcont.emplace_back(orbcont.front());
362
363 for(auto &v : orbcont) v = -v;
364
365 }
366
367 // Copy the orbiter (contour only), we will have to work on it
368 RawShape orbiter;
369 sl::contour(orbiter) = orbcont;
370
371 // An edge with additional data for marking it
372 struct MarkedEdge {
373 Edge e; Radians turn_angle = 0; bool is_turning_point = false;
374 MarkedEdge() = default;
375 MarkedEdge(const Edge& ed, Radians ta, bool tp):
376 e(ed), turn_angle(ta), is_turning_point(tp) {}
377
378 // debug
379 std::string label;
380 };
381
382 // Container for marked edges
383 using EdgeList = vector<MarkedEdge>;
384
385 EdgeList A, B;
386
387 // This is how an edge list is created from the polygons
388 auto fillEdgeList = [](EdgeList& L, const RawShape& ppoly, int dir) {
389 auto& poly = sl::contour(ppoly);
390
391 L.reserve(sl::contourVertexCount(poly));
392
393 if(dir > 0) {
394 auto it = poly.begin();
395 auto nextit = std::next(it);
396
397 double turn_angle = 0;
398 bool is_turn_point = false;
399
400 while(nextit != poly.end()) {
401 L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
402 it++; nextit++;
403 }
404 } else {
405 auto it = sl::rbegin(poly);
406 auto nextit = std::next(it);
407
408 double turn_angle = 0;
409 bool is_turn_point = false;
410
411 while(nextit != sl::rend(poly)) {
412 L.emplace_back(Edge(*it, *nextit), turn_angle, is_turn_point);
413 it++; nextit++;
414 }
415 }
416
417 auto getTurnAngle = [](const Edge& e1, const Edge& e2) {
418 auto phi = e1.angleToXaxis();
419 auto phi_prev = e2.angleToXaxis();
420 auto turn_angle = phi-phi_prev;
421 if(turn_angle > Pi) turn_angle -= TwoPi;
422 if(turn_angle < -Pi) turn_angle += TwoPi;
423 return turn_angle;
424 };
425
426 auto eit = L.begin();
427 auto enext = std::next(eit);
428
429 eit->turn_angle = getTurnAngle(L.front().e, L.back().e);
430
431 while(enext != L.end()) {
432 enext->turn_angle = getTurnAngle( enext->e, eit->e);
433 eit->is_turning_point =
434 signbit(enext->turn_angle) != signbit(eit->turn_angle);
435 ++eit; ++enext;
436 }
437
438 L.back().is_turning_point = signbit(L.back().turn_angle) !=
439 signbit(L.front().turn_angle);
440
441 };
442
443 // Step 2: Fill the edgelists
444 fillEdgeList(A, stationary, 1);
445 fillEdgeList(B, orbiter, 1);
446
447 int i = 1;
448 for(MarkedEdge& me : A) {
449 std::cout << "a" << i << ":\n\t"
450 << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
451 << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
452 << "Turning point: " << (me.is_turning_point ? "yes" : "no")
453 << std::endl;
454
455 me.label = "a"; me.label += std::to_string(i);
456 i++;
457 }
458
459 i = 1;
460 for(MarkedEdge& me : B) {
461 std::cout << "b" << i << ":\n\t"
462 << getX(me.e.first()) << " " << getY(me.e.first()) << "\n\t"
463 << getX(me.e.second()) << " " << getY(me.e.second()) << "\n\t"
464 << "Turning point: " << (me.is_turning_point ? "yes" : "no")
465 << std::endl;
466 me.label = "b"; me.label += std::to_string(i);
467 i++;
468 }
469
470 // A reference to a marked edge that also knows its container
471 struct MarkedEdgeRef {
472 reference_wrapper<MarkedEdge> eref;
473 reference_wrapper<vector<MarkedEdgeRef>> container;
474 Coord dir = 1; // Direction modifier
475
476 inline Radians angleX() const { return eref.get().e.angleToXaxis(); }
477 inline const Edge& edge() const { return eref.get().e; }
478 inline Edge& edge() { return eref.get().e; }
479 inline bool isTurningPoint() const {
480 return eref.get().is_turning_point;
481 }
482 inline bool isFrom(const vector<MarkedEdgeRef>& cont ) {
483 return &(container.get()) == &cont;
484 }
485 inline bool eq(const MarkedEdgeRef& mr) {
486 return &(eref.get()) == &(mr.eref.get());
487 }
488
489 MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
490 reference_wrapper<vector<MarkedEdgeRef>> ec):
491 eref(er), container(ec), dir(1) {}
492
493 MarkedEdgeRef(reference_wrapper<MarkedEdge> er,
494 reference_wrapper<vector<MarkedEdgeRef>> ec,
495 Coord d):
496 eref(er), container(ec), dir(d) {}
497 };
498
499 using EdgeRefList = vector<MarkedEdgeRef>;
500
501 // Comparing two marked edges
502 auto sortfn = [](const MarkedEdgeRef& e1, const MarkedEdgeRef& e2) {
503 return e1.angleX() < e2.angleX();
504 };
505
506 EdgeRefList Aref, Bref; // We create containers for the references
507 Aref.reserve(A.size()); Bref.reserve(B.size());
508
509 // Fill reference container for the stationary polygon
510 std::for_each(A.begin(), A.end(), [&Aref](MarkedEdge& me) {
511 Aref.emplace_back( ref(me), ref(Aref) );
512 });
513
514 // Fill reference container for the orbiting polygon
515 std::for_each(B.begin(), B.end(), [&Bref](MarkedEdge& me) {
516 Bref.emplace_back( ref(me), ref(Bref) );
517 });
518
519 auto mink = [sortfn] // the Mink(Q, R, direction) sub-procedure
520 (const EdgeRefList& Q, const EdgeRefList& R, bool positive)
521 {
522
523 // Step 1 "merge sort_list(Q) and sort_list(R) to form merge_list(Q,R)"
524 // Sort the containers of edge references and merge them.
525 // Q could be sorted only once and be reused here but we would still
526 // need to merge it with sorted(R).
527
528 EdgeRefList merged;
529 EdgeRefList S, seq;
530 merged.reserve(Q.size() + R.size());
531
532 merged.insert(merged.end(), R.begin(), R.end());
533 std::stable_sort(merged.begin(), merged.end(), sortfn);
534 merged.insert(merged.end(), Q.begin(), Q.end());
535 std::stable_sort(merged.begin(), merged.end(), sortfn);
536
537 // Step 2 "set i = 1, k = 1, direction = 1, s1 = q1"
538 // we don't use i, instead, q is an iterator into Q. k would be an index
539 // into the merged sequence but we use "it" as an iterator for that
540
541 // here we obtain references for the containers for later comparisons
542 const auto& Rcont = R.begin()->container.get();
543 const auto& Qcont = Q.begin()->container.get();
544
545 // Set the initial direction
546 Coord dir = 1;
547
548 // roughly i = 1 (so q = Q.begin()) and s1 = q1 so S[0] = q;
549 if(positive) {
550 auto q = Q.begin();
551 S.emplace_back(*q);
552
553 // Roughly step 3
554
555 std::cout << "merged size: " << merged.size() << std::endl;
556 auto mit = merged.begin();
557 for(bool finish = false; !finish && q != Q.end();) {
558 ++q; // "Set i = i + 1"
559
560 while(!finish && mit != merged.end()) {
561 if(mit->isFrom(Rcont)) {
562 auto s = *mit;
563 s.dir = dir;
564 S.emplace_back(s);
565 }
566
567 if(mit->eq(*q)) {
568 S.emplace_back(*q);
569 if(mit->isTurningPoint()) dir = -dir;
570 if(q == Q.begin()) finish = true;
571 break;
572 }
573
574 mit += dir;
575 // __nfp::advance(mit, merged, dir > 0);
576 }
577 }
578 } else {
579 auto q = Q.rbegin();
580 S.emplace_back(*q);
581
582 // Roughly step 3
583
584 std::cout << "merged size: " << merged.size() << std::endl;
585 auto mit = merged.begin();
586 for(bool finish = false; !finish && q != Q.rend();) {
587 ++q; // "Set i = i + 1"
588
589 while(!finish && mit != merged.end()) {
590 if(mit->isFrom(Rcont)) {
591 auto s = *mit;
592 s.dir = dir;
593 S.emplace_back(s);
594 }
595
596 if(mit->eq(*q)) {
597 S.emplace_back(*q);
598 S.back().dir = -1;
599 if(mit->isTurningPoint()) dir = -dir;
600 if(q == Q.rbegin()) finish = true;
601 break;
602 }
603
604 mit += dir;
605 // __nfp::advance(mit, merged, dir > 0);
606 }
607 }
608 }
609
610
611 // Step 4:
612
613 // "Let starting edge r1 be in position si in sequence"
614 // whaaat? I guess this means the following:
615 auto it = S.begin();
616 while(!it->eq(*R.begin())) ++it;
617
618 // "Set j = 1, next = 2, direction = 1, seq1 = si"
619 // we don't use j, seq is expanded dynamically.
620 dir = 1;
621 auto next = std::next(R.begin()); seq.emplace_back(*it);
622
623 // Step 5:
624 // "If all si edges have been allocated to seqj" should mean that
625 // we loop until seq has equal size with S
626 auto send = it; //it == S.begin() ? it : std::prev(it);
627 while(it != S.end()) {
628 ++it; if(it == S.end()) it = S.begin();
629 if(it == send) break;
630
631 if(it->isFrom(Qcont)) {
632 seq.emplace_back(*it); // "If si is from Q, j = j + 1, seqj = si"
633
634 // "If si is a turning point in Q,
635 // direction = - direction, next = next + direction"
636 if(it->isTurningPoint()) {
637 dir = -dir;
638 next += dir;
639 // __nfp::advance(next, R, dir > 0);
640 }
641 }
642
643 if(it->eq(*next) /*&& dir == next->dir*/) { // "If si = direction.rnext"
644 // "j = j + 1, seqj = si, next = next + direction"
645 seq.emplace_back(*it);
646 next += dir;
647 // __nfp::advance(next, R, dir > 0);
648 }
649 }
650
651 return seq;
652 };
653
654 std::vector<EdgeRefList> seqlist;
655 seqlist.reserve(Bref.size());
656
657 EdgeRefList Bslope = Bref; // copy Bref, we will make a slope diagram
658
659 // make the slope diagram of B
660 std::sort(Bslope.begin(), Bslope.end(), sortfn);
661
662 auto slopeit = Bslope.begin(); // search for the first turning point
663 while(!slopeit->isTurningPoint() && slopeit != Bslope.end()) slopeit++;
664
665 if(slopeit == Bslope.end()) {
666 // no turning point means convex polygon.
667 seqlist.emplace_back(mink(Aref, Bref, true));
668 } else {
669 int dir = 1;
670
671 auto firstturn = Bref.begin();
672 while(!firstturn->eq(*slopeit)) ++firstturn;
673
674 assert(firstturn != Bref.end());
675
676 EdgeRefList bgroup; bgroup.reserve(Bref.size());
677 bgroup.emplace_back(*slopeit);
678
679 auto b_it = std::next(firstturn);
680 while(b_it != firstturn) {
681 if(b_it == Bref.end()) b_it = Bref.begin();
682
683 while(!slopeit->eq(*b_it)) {
684 __nfp::advance(slopeit, Bslope, dir > 0);
685 }
686
687 if(!slopeit->isTurningPoint()) {
688 bgroup.emplace_back(*slopeit);
689 } else {
690 if(!bgroup.empty()) {
691 if(dir > 0) bgroup.emplace_back(*slopeit);
692 for(auto& me : bgroup) {
693 std::cout << me.eref.get().label << ", ";
694 }
695 std::cout << std::endl;
696 seqlist.emplace_back(mink(Aref, bgroup, dir == 1 ? true : false));
697 bgroup.clear();
698 if(dir < 0) bgroup.emplace_back(*slopeit);
699 } else {
700 bgroup.emplace_back(*slopeit);
701 }
702
703 dir *= -1;
704 }
705 ++b_it;
706 }
707 }
708
709 // while(it != Bref.end()) // This is step 3 and step 4 in one loop
710 // if(it->isTurningPoint()) {
711 // R = {R.last, it++};
712 // auto seq = mink(Q, R, orientation);
713
714 // // TODO step 6 (should be 5 shouldn't it?): linking edges from A
715 // // I don't get this step
716
717 // seqlist.insert(seqlist.end(), seq.begin(), seq.end());
718 // orientation = !orientation;
719 // } else ++it;
720
721 // if(seqlist.empty()) seqlist = mink(Q, {Bref.begin(), Bref.end()}, true);
722
723 // /////////////////////////////////////////////////////////////////////////
724 // Algorithm 2: breaking Minkowski sums into track line trips
725 // /////////////////////////////////////////////////////////////////////////
726
727
728 // /////////////////////////////////////////////////////////////////////////
729 // Algorithm 3: finding the boundary of the NFP from track line trips
730 // /////////////////////////////////////////////////////////////////////////
731
732
733 for(auto& seq : seqlist) {
734 std::cout << "seqlist size: " << seq.size() << std::endl;
735 for(auto& s : seq) {
736 std::cout << (s.dir > 0 ? "" : "-") << s.eref.get().label << ", ";
737 }
738 std::cout << std::endl;
739 }
740
741 auto& seq = seqlist.front();
742 RawShape rsh;
743 Vertex top_nfp;
744 std::vector<Edge> edgelist; edgelist.reserve(seq.size());
745 for(auto& s : seq) {
746 edgelist.emplace_back(s.eref.get().e);
747 }
748
749 __nfp::buildPolygon(edgelist, rsh, top_nfp);
750
751 return Result(rsh, top_nfp);
752 }
753
754 // Specializable NFP implementation class. Specialize it if you have a faster
755 // or better NFP implementation
756 template<class RawShape, NfpLevel nfptype>
757 struct NfpImpl {
operator ()libnest2d::nfp::NfpImpl758 NfpResult<RawShape> operator()(const RawShape& sh, const RawShape& other)
759 {
760 static_assert(nfptype == NfpLevel::CONVEX_ONLY,
761 "Nfp::noFitPolygon() unimplemented!");
762
763 // Libnest2D has a default implementation for convex polygons and will
764 // use it if feasible.
765 return nfpConvexOnly(sh, other);
766 }
767 };
768
769 /// Helper function to get the NFP
770 template<NfpLevel nfptype, class RawShape>
noFitPolygon(const RawShape & sh,const RawShape & other)771 inline NfpResult<RawShape> noFitPolygon(const RawShape& sh,
772 const RawShape& other)
773 {
774 NfpImpl<RawShape, nfptype> nfps;
775 return nfps(sh, other);
776 }
777
778 }
779
780 }
781
782 #endif // GEOMETRIES_NOFITPOLYGON_HPP
783