1 #ifndef CLIPPER_BACKEND_HPP
2 #define CLIPPER_BACKEND_HPP
3 
4 #include <sstream>
5 #include <unordered_map>
6 #include <cassert>
7 #include <vector>
8 #include <iostream>
9 
10 #include <libnest2d/geometry_traits.hpp>
11 #include <libnest2d/geometry_traits_nfp.hpp>
12 
13 #include "clipper_polygon.hpp"
14 
15 namespace libnest2d {
16 
17 // Aliases for convinience
18 using PointImpl = ClipperLib::IntPoint;
19 using PathImpl  = ClipperLib::Path;
20 using HoleStore = ClipperLib::Paths;
21 using PolygonImpl = ClipperLib::Polygon;
22 
23 template<> struct ShapeTag<PolygonImpl> { using Type = PolygonTag; };
24 template<> struct ShapeTag<PathImpl>    { using Type = PathTag; };
25 template<> struct ShapeTag<PointImpl>   { using Type = PointTag; };
26 
27 // Type of coordinate units used by Clipper. Enough to specialize for point,
28 // the rest of the types will work (Path, Polygon)
29 template<> struct CoordType<PointImpl> { using Type = ClipperLib::cInt; };
30 
31 // Enough to specialize for path, it will work for multishape and Polygon
32 template<> struct PointType<PathImpl> { using Type = PointImpl; };
33 
34 // This is crucial. CountourType refers to itself by default, so we don't have
35 // to secialize for clipper Path. ContourType<PathImpl>::Type is PathImpl.
36 template<> struct ContourType<PolygonImpl> { using Type = PathImpl; };
37 
38 // The holes are contained in Clipper::Paths
39 template<> struct HolesContainer<PolygonImpl> { using Type = ClipperLib::Paths; };
40 
41 namespace pointlike {
42 
43 // Tell libnest2d how to extract the X coord from a ClipperPoint object
x(const PointImpl & p)44 template<> inline ClipperLib::cInt x(const PointImpl& p)
45 {
46     return p.X;
47 }
48 
49 // Tell libnest2d how to extract the Y coord from a ClipperPoint object
y(const PointImpl & p)50 template<> inline ClipperLib::cInt y(const PointImpl& p)
51 {
52     return p.Y;
53 }
54 
55 // Tell libnest2d how to extract the X coord from a ClipperPoint object
x(PointImpl & p)56 template<> inline ClipperLib::cInt& x(PointImpl& p)
57 {
58     return p.X;
59 }
60 
61 // Tell libnest2d how to extract the Y coord from a ClipperPoint object
y(PointImpl & p)62 template<> inline ClipperLib::cInt& y(PointImpl& p)
63 {
64     return p.Y;
65 }
66 
67 }
68 
69 // Using the libnest2d default area implementation
70 #define DISABLE_BOOST_AREA
71 
72 namespace shapelike {
73 
74 template<>
offset(PolygonImpl & sh,TCoord<PointImpl> distance,const PolygonTag &)75 inline void offset(PolygonImpl& sh, TCoord<PointImpl> distance, const PolygonTag&)
76 {
77     #define DISABLE_BOOST_OFFSET
78 
79     using ClipperLib::ClipperOffset;
80     using ClipperLib::jtSquare;
81     using ClipperLib::etClosedPolygon;
82     using ClipperLib::Paths;
83 
84     Paths result;
85 
86     try {
87         ClipperOffset offs;
88         offs.AddPath(sh.Contour, jtSquare, etClosedPolygon);
89         offs.AddPaths(sh.Holes, jtSquare, etClosedPolygon);
90         offs.Execute(result, static_cast<double>(distance));
91     } catch (ClipperLib::clipperException &) {
92         throw GeometryException(GeomErr::OFFSET);
93     }
94 
95     // Offsetting reverts the orientation and also removes the last vertex
96     // so boost will not have a closed polygon.
97 
98     bool found_the_contour = false;
99     for(auto& r : result) {
100         if(ClipperLib::Orientation(r)) {
101             // We don't like if the offsetting generates more than one contour
102             // but throwing would be an overkill. Instead, we should warn the
103             // caller about the inability to create correct geometries
104             if(!found_the_contour) {
105                 sh.Contour = std::move(r);
106                 ClipperLib::ReversePath(sh.Contour);
107                 auto front_p = sh.Contour.front();
108                 sh.Contour.emplace_back(std::move(front_p));
109                 found_the_contour = true;
110             } else {
111                 dout() << "Warning: offsetting result is invalid!";
112                 /* TODO warning */
113             }
114         } else {
115             // TODO If there are multiple contours we can't be sure which hole
116             // belongs to the first contour. (But in this case the situation is
117             // bad enough to let it go...)
118             sh.Holes.emplace_back(std::move(r));
119             ClipperLib::ReversePath(sh.Holes.back());
120             auto front_p = sh.Holes.back().front();
121             sh.Holes.back().emplace_back(std::move(front_p));
122         }
123     }
124 }
125 
126 template<>
offset(PathImpl & sh,TCoord<PointImpl> distance,const PathTag &)127 inline void offset(PathImpl& sh, TCoord<PointImpl> distance, const PathTag&)
128 {
129     PolygonImpl p(std::move(sh));
130     offset(p, distance, PolygonTag());
131     sh = p.Contour;
132 }
133 
134 // Tell libnest2d how to make string out of a ClipperPolygon object
toString(const PolygonImpl & sh)135 template<> inline std::string toString(const PolygonImpl& sh)
136 {
137     std::stringstream ss;
138 
139     ss << "Contour {\n";
140     for(auto p : sh.Contour) {
141         ss << "\t" << p.X << " " << p.Y << "\n";
142     }
143     ss << "}\n";
144 
145     for(auto& h : sh.Holes) {
146         ss << "Holes {\n";
147         for(auto p : h)  {
148             ss << "\t{\n";
149             ss << "\t\t" << p.X << " " << p.Y << "\n";
150             ss << "\t}\n";
151         }
152         ss << "}\n";
153     }
154 
155     return ss.str();
156 }
157 
158 template<>
create(const PathImpl & path,const HoleStore & holes)159 inline PolygonImpl create(const PathImpl& path, const HoleStore& holes)
160 {
161     PolygonImpl p;
162     p.Contour = path;
163     p.Holes = holes;
164 
165     return p;
166 }
167 
create(PathImpl && path,HoleStore && holes)168 template<> inline PolygonImpl create( PathImpl&& path, HoleStore&& holes) {
169     PolygonImpl p;
170     p.Contour.swap(path);
171     p.Holes.swap(holes);
172 
173     return p;
174 }
175 
176 template<>
holes(const PolygonImpl & sh)177 inline const THolesContainer<PolygonImpl>& holes(const PolygonImpl& sh)
178 {
179     return sh.Holes;
180 }
181 
holes(PolygonImpl & sh)182 template<> inline THolesContainer<PolygonImpl>& holes(PolygonImpl& sh)
183 {
184     return sh.Holes;
185 }
186 
187 template<>
hole(PolygonImpl & sh,unsigned long idx)188 inline TContour<PolygonImpl>& hole(PolygonImpl& sh, unsigned long idx)
189 {
190     return sh.Holes[idx];
191 }
192 
193 template<>
hole(const PolygonImpl & sh,unsigned long idx)194 inline const TContour<PolygonImpl>& hole(const PolygonImpl& sh,
195                                             unsigned long idx)
196 {
197     return sh.Holes[idx];
198 }
199 
holeCount(const PolygonImpl & sh)200 template<> inline size_t holeCount(const PolygonImpl& sh)
201 {
202     return sh.Holes.size();
203 }
204 
contour(PolygonImpl & sh)205 template<> inline PathImpl& contour(PolygonImpl& sh)
206 {
207     return sh.Contour;
208 }
209 
210 template<>
contour(const PolygonImpl & sh)211 inline const PathImpl& contour(const PolygonImpl& sh)
212 {
213     return sh.Contour;
214 }
215 
216 #define DISABLE_BOOST_TRANSLATE
217 template<>
translate(PolygonImpl & sh,const PointImpl & offs)218 inline void translate(PolygonImpl& sh, const PointImpl& offs)
219 {
220     for(auto& p : sh.Contour) { p += offs; }
221     for(auto& hole : sh.Holes) for(auto& p : hole) { p += offs; }
222 }
223 
224 #define DISABLE_BOOST_ROTATE
225 template<>
rotate(PolygonImpl & sh,const Radians & rads)226 inline void rotate(PolygonImpl& sh, const Radians& rads)
227 {
228     using Coord = TCoord<PointImpl>;
229 
230     auto cosa = rads.cos();
231     auto sina = rads.sin();
232 
233     for(auto& p : sh.Contour) {
234         p = {
235                 static_cast<Coord>(p.X * cosa - p.Y * sina),
236                 static_cast<Coord>(p.X * sina + p.Y * cosa)
237             };
238     }
239     for(auto& hole : sh.Holes) for(auto& p : hole) {
240         p = {
241                 static_cast<Coord>(p.X * cosa - p.Y * sina),
242                 static_cast<Coord>(p.X * sina + p.Y * cosa)
243             };
244     }
245 }
246 
247 } // namespace shapelike
248 
249 #define DISABLE_BOOST_NFP_MERGE
clipper_execute(ClipperLib::Clipper & clipper,ClipperLib::ClipType clipType,ClipperLib::PolyFillType subjFillType=ClipperLib::pftEvenOdd,ClipperLib::PolyFillType clipFillType=ClipperLib::pftEvenOdd)250 inline TMultiShape<PolygonImpl> clipper_execute(
251         ClipperLib::Clipper& clipper,
252         ClipperLib::ClipType clipType,
253         ClipperLib::PolyFillType subjFillType = ClipperLib::pftEvenOdd,
254         ClipperLib::PolyFillType clipFillType = ClipperLib::pftEvenOdd)
255 {
256     TMultiShape<PolygonImpl> retv;
257 
258     ClipperLib::PolyTree result;
259     clipper.Execute(clipType, result, subjFillType, clipFillType);
260 
261     retv.reserve(static_cast<size_t>(result.Total()));
262 
263     std::function<void(ClipperLib::PolyNode*, PolygonImpl&)> processHole;
264 
265     auto processPoly = [&retv, &processHole](ClipperLib::PolyNode *pptr) {
266         PolygonImpl poly;
267         poly.Contour.swap(pptr->Contour);
268 
269         assert(!pptr->IsHole());
270 
271         if(!poly.Contour.empty() ) {
272             auto front_p = poly.Contour.front();
273             auto &back_p  = poly.Contour.back();
274             if(front_p.X != back_p.X || front_p.Y != back_p.X)
275                 poly.Contour.emplace_back(front_p);
276         }
277 
278         for(auto h : pptr->Childs) { processHole(h, poly); }
279         retv.push_back(poly);
280     };
281 
282     processHole = [&processPoly](ClipperLib::PolyNode *pptr, PolygonImpl& poly)
283     {
284         poly.Holes.emplace_back(std::move(pptr->Contour));
285 
286         assert(pptr->IsHole());
287 
288         if(!poly.Contour.empty() ) {
289             auto front_p = poly.Contour.front();
290             auto &back_p  = poly.Contour.back();
291             if(front_p.X != back_p.X || front_p.Y != back_p.X)
292                 poly.Contour.emplace_back(front_p);
293         }
294 
295         for(auto c : pptr->Childs) processPoly(c);
296     };
297 
298     auto traverse = [&processPoly] (ClipperLib::PolyNode *node)
299     {
300         for(auto ch : node->Childs) processPoly(ch);
301     };
302 
303     traverse(&result);
304 
305     return retv;
306 }
307 
308 namespace nfp {
309 
310 template<> inline TMultiShape<PolygonImpl>
merge(const TMultiShape<PolygonImpl> & shapes)311 merge(const TMultiShape<PolygonImpl>& shapes)
312 {
313     ClipperLib::Clipper clipper(ClipperLib::ioReverseSolution);
314 
315     bool closed = true;
316     bool valid = true;
317 
318     for(auto& path : shapes) {
319         valid &= clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
320 
321         for(auto& h : path.Holes)
322             valid &= clipper.AddPath(h, ClipperLib::ptSubject, closed);
323     }
324 
325     if(!valid) throw GeometryException(GeomErr::MERGE);
326 
327     return clipper_execute(clipper, ClipperLib::ctUnion, ClipperLib::pftNegative);
328 }
329 
330 }
331 
332 }
333 
334 #define DISABLE_BOOST_CONVEX_HULL
335 
336 //#define DISABLE_BOOST_SERIALIZE
337 //#define DISABLE_BOOST_UNSERIALIZE
338 
339 #ifdef _MSC_VER
340 #pragma warning(push)
341 #pragma warning(disable: 4244)
342 #pragma warning(disable: 4267)
343 #endif
344 // All other operators and algorithms are implemented with boost
345 #include <libnest2d/utils/boost_alg.hpp>
346 #ifdef _MSC_VER
347 #pragma warning(pop)
348 #endif
349 
350 #endif // CLIPPER_BACKEND_HPP
351