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