1 // Optimize the extrusion simulator to the bones.
2 //#pragma GCC optimize ("O3")
3 //#undef SLIC3R_DEBUG
4 //#define NDEBUG
5
6 #include <cmath>
7 #include <cassert>
8
9 #include <boost/geometry.hpp>
10 #include <boost/geometry/geometries/box.hpp>
11 #include <boost/geometry/geometries/point.hpp>
12 #include <boost/geometry/geometries/point_xy.hpp>
13
14 #include <boost/multi_array.hpp>
15
16 #include "libslic3r.h"
17 #include "ExtrusionSimulator.hpp"
18
19 #ifndef M_PI
20 #define M_PI 3.1415926535897932384626433832795
21 #endif
22
23 namespace Slic3r {
24
25 // Replacement for a template alias.
26 // Shorthand for the point_xy.
27 template<typename T>
28 struct V2
29 {
30 typedef boost::geometry::model::d2::point_xy<T> Type;
31 };
32
33 // Replacement for a template alias.
34 // Shorthand for the point with a cartesian coordinate system.
35 template<typename T>
36 struct V3
37 {
38 typedef boost::geometry::model::point<T, 3, boost::geometry::cs::cartesian> Type;
39 };
40
41 // Replacement for a template alias.
42 // Shorthand for the point with a cartesian coordinate system.
43 template<typename T>
44 struct V4
45 {
46 typedef boost::geometry::model::point<T, 4, boost::geometry::cs::cartesian> Type;
47 };
48
49 typedef V2<int >::Type V2i;
50 typedef V2<float >::Type V2f;
51 typedef V2<double>::Type V2d;
52
53 // Used for an RGB color.
54 typedef V3<unsigned char>::Type V3uc;
55 // Used for an RGBA color.
56 typedef V4<unsigned char>::Type V4uc;
57
58 typedef boost::geometry::model::box<V2i> B2i;
59 typedef boost::geometry::model::box<V2f> B2f;
60 typedef boost::geometry::model::box<V2d> B2d;
61
62 typedef boost::multi_array<unsigned char, 2> A2uc;
63 typedef boost::multi_array<int , 2> A2i;
64 typedef boost::multi_array<float , 2> A2f;
65 typedef boost::multi_array<double , 2> A2d;
66
67 template<typename T>
operator +=(boost::geometry::model::d2::point_xy<T> & v1,const boost::geometry::model::d2::point_xy<T> & v2)68 inline void operator+=(
69 boost::geometry::model::d2::point_xy<T> &v1,
70 const boost::geometry::model::d2::point_xy<T> &v2)
71 {
72 boost::geometry::add_point(v1, v2);
73 }
74
75 template<typename T>
operator -=(boost::geometry::model::d2::point_xy<T> & v1,const boost::geometry::model::d2::point_xy<T> & v2)76 inline void operator-=(
77 boost::geometry::model::d2::point_xy<T> &v1,
78 const boost::geometry::model::d2::point_xy<T> &v2)
79 {
80 boost::geometry::subtract_point(v1, v2);
81 }
82
83 template<typename T>
operator *=(boost::geometry::model::d2::point_xy<T> & v,const T c)84 inline void operator*=(boost::geometry::model::d2::point_xy<T> &v, const T c)
85 {
86 boost::geometry::multiply_value(v, c);
87 }
88
89 template<typename T>
operator /=(boost::geometry::model::d2::point_xy<T> & v,const T c)90 inline void operator/=(boost::geometry::model::d2::point_xy<T> &v, const T c)
91 {
92 boost::geometry::divide_value(v, c);
93 }
94
95 template<typename T>
operator +(const boost::geometry::model::d2::point_xy<T> & v1,const boost::geometry::model::d2::point_xy<T> & v2)96 inline typename boost::geometry::model::d2::point_xy<T> operator+(
97 const boost::geometry::model::d2::point_xy<T> &v1,
98 const boost::geometry::model::d2::point_xy<T> &v2)
99 {
100 boost::geometry::model::d2::point_xy<T> out(v1);
101 out += v2;
102 return out;
103 }
104
105 template<typename T>
operator -(const boost::geometry::model::d2::point_xy<T> & v1,const boost::geometry::model::d2::point_xy<T> & v2)106 inline boost::geometry::model::d2::point_xy<T> operator-(
107 const boost::geometry::model::d2::point_xy<T> &v1,
108 const boost::geometry::model::d2::point_xy<T> &v2)
109 {
110 boost::geometry::model::d2::point_xy<T> out(v1);
111 out -= v2;
112 return out;
113 }
114
115 template<typename T>
operator *(const boost::geometry::model::d2::point_xy<T> & v,const T c)116 inline boost::geometry::model::d2::point_xy<T> operator*(
117 const boost::geometry::model::d2::point_xy<T> &v, const T c)
118 {
119 boost::geometry::model::d2::point_xy<T> out(v);
120 out *= c;
121 return out;
122 }
123
124 template<typename T>
operator *(const T c,const boost::geometry::model::d2::point_xy<T> & v)125 inline typename boost::geometry::model::d2::point_xy<T> operator*(
126 const T c, const boost::geometry::model::d2::point_xy<T> &v)
127 {
128 boost::geometry::model::d2::point_xy<T> out(v);
129 out *= c;
130 return out;
131 }
132
133 template<typename T>
operator /(const boost::geometry::model::d2::point_xy<T> & v,const T c)134 inline typename boost::geometry::model::d2::point_xy<T> operator/(
135 const boost::geometry::model::d2::point_xy<T> &v, const T c)
136 {
137 boost::geometry::model::d2::point_xy<T> out(v);
138 out /= c;
139 return out;
140 }
141
142 template<typename T>
dot(const boost::geometry::model::d2::point_xy<T> & v1,const boost::geometry::model::d2::point_xy<T> & v2)143 inline T dot(
144 const boost::geometry::model::d2::point_xy<T> &v1,
145 const boost::geometry::model::d2::point_xy<T> &v2)
146 {
147 return boost::geometry::dot_product(v1, v2);
148 }
149
150 template<typename T>
dot(const boost::geometry::model::d2::point_xy<T> & v)151 inline T dot(const boost::geometry::model::d2::point_xy<T> &v)
152 {
153 return boost::geometry::dot_product(v, v);
154 }
155
156 template <typename T>
cross(const boost::geometry::model::d2::point_xy<T> & v1,const boost::geometry::model::d2::point_xy<T> & v2)157 inline T cross(
158 const boost::geometry::model::d2::point_xy<T> &v1,
159 const boost::geometry::model::d2::point_xy<T> &v2)
160 {
161 return v1.x() * v2.y() - v2.x() * v1.y();
162 }
163
164 // Euclidian measure
165 template<typename T>
l2(const boost::geometry::model::d2::point_xy<T> & v)166 inline T l2(const boost::geometry::model::d2::point_xy<T> &v)
167 {
168 return std::sqrt(dot(v));
169 }
170
171 // Euclidian measure
172 template<typename T>
mag(const boost::geometry::model::d2::point_xy<T> & v)173 inline T mag(const boost::geometry::model::d2::point_xy<T> &v)
174 {
175 return l2(v);
176 }
177
178 template<typename T>
dist2_to_line(const boost::geometry::model::d2::point_xy<T> & p0,const boost::geometry::model::d2::point_xy<T> & p1,const boost::geometry::model::d2::point_xy<T> & px)179 inline T dist2_to_line(
180 const boost::geometry::model::d2::point_xy<T> &p0,
181 const boost::geometry::model::d2::point_xy<T> &p1,
182 const boost::geometry::model::d2::point_xy<T> &px)
183 {
184 boost::geometry::model::d2::point_xy<T> v = p1 - p0;
185 boost::geometry::model::d2::point_xy<T> vx = px - p0;
186 T l = dot(v);
187 T t = dot(v, vx);
188 if (l != T(0) && t > T(0.)) {
189 t /= l;
190 vx = px - ((t > T(1.)) ? p1 : (p0 + t * v));
191 }
192 return dot(vx);
193 }
194
195 // Intersect a circle with a line segment.
196 // Returns number of intersection points.
197 template<typename T>
line_circle_intersection(const boost::geometry::model::d2::point_xy<T> & p0,const boost::geometry::model::d2::point_xy<T> & p1,const boost::geometry::model::d2::point_xy<T> & center,const T radius,boost::geometry::model::d2::point_xy<T> intersection[2])198 int line_circle_intersection(
199 const boost::geometry::model::d2::point_xy<T> &p0,
200 const boost::geometry::model::d2::point_xy<T> &p1,
201 const boost::geometry::model::d2::point_xy<T> ¢er,
202 const T radius,
203 boost::geometry::model::d2::point_xy<T> intersection[2])
204 {
205 typedef typename V2<T>::Type V2T;
206 V2T v = p1 - p0;
207 V2T vc = p0 - center;
208 T a = dot(v);
209 T b = T(2.) * dot(vc, v);
210 T c = dot(vc) - radius * radius;
211 T d = b * b - T(4.) * a * c;
212
213 if (d < T(0))
214 // The circle misses the ray.
215 return 0;
216
217 int n = 0;
218 if (d == T(0)) {
219 // The circle touches the ray at a single tangent point.
220 T t = - b / (T(2.) * a);
221 if (t >= T(0.) && t <= T(1.))
222 intersection[n ++] = p0 + t * v;
223 } else {
224 // The circle intersects the ray in two points.
225 d = sqrt(d);
226 T t = (- b - d) / (T(2.) * a);
227 if (t >= T(0.) && t <= T(1.))
228 intersection[n ++] = p0 + t * v;
229 t = (- b + d) / (T(2.) * a);
230 if (t >= T(0.) && t <= T(1.))
231 intersection[n ++] = p0 + t * v;
232 }
233 return n;
234 }
235
236 // Sutherland–Hodgman clipping of a rectangle against an AABB.
237 // Expects the first 4 points of rect to be filled at the beginning.
238 // The clipping may produce up to 8 points.
239 // Returns the number of resulting points.
240 template<typename T>
clip_rect_by_AABB(boost::geometry::model::d2::point_xy<T> rect[8],const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T>> & aabb)241 int clip_rect_by_AABB(
242 boost::geometry::model::d2::point_xy<T> rect[8],
243 const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T> > &aabb)
244 {
245 typedef typename V2<T>::Type V2T;
246 V2T result[8];
247 int nin = 4;
248 int nout = 0;
249 V2T *in = rect;
250 V2T *out = result;
251 // Clip left
252 {
253 const V2T *S = in + nin - 1;
254 T left = aabb.min_corner().x();
255 for (int i = 0; i < nin; ++i) {
256 const V2T &E = in[i];
257 if (E.x() == left) {
258 out[nout++] = E;
259 }
260 else if (E.x() > left) {
261 // E is inside the AABB.
262 if (S->x() < left) {
263 // S is outside the AABB. Calculate an intersection point.
264 T t = (left - S->x()) / (E.x() - S->x());
265 out[nout++] = V2T(left, S->y() + t * (E.y() - S->y()));
266 }
267 out[nout++] = E;
268 }
269 else if (S->x() > left) {
270 // S is inside the AABB, E is outside the AABB.
271 T t = (left - S->x()) / (E.x() - S->x());
272 out[nout++] = V2T(left, S->y() + t * (E.y() - S->y()));
273 }
274 S = &E;
275 }
276 assert(nout <= 8);
277 }
278 // Clip bottom
279 {
280 std::swap(in, out);
281 nin = nout;
282 nout = 0;
283 const V2T *S = in + nin - 1;
284 T bottom = aabb.min_corner().y();
285 for (int i = 0; i < nin; ++i) {
286 const V2T &E = in[i];
287 if (E.y() == bottom) {
288 out[nout++] = E;
289 }
290 else if (E.y() > bottom) {
291 // E is inside the AABB.
292 if (S->y() < bottom) {
293 // S is outside the AABB. Calculate an intersection point.
294 T t = (bottom - S->y()) / (E.y() - S->y());
295 out[nout++] = V2T(S->x() + t * (E.x() - S->x()), bottom);
296 }
297 out[nout++] = E;
298 }
299 else if (S->y() > bottom) {
300 // S is inside the AABB, E is outside the AABB.
301 T t = (bottom - S->y()) / (E.y() - S->y());
302 out[nout++] = V2T(S->x() + t * (E.x() - S->x()), bottom);
303 }
304 S = &E;
305 }
306 assert(nout <= 8);
307 }
308 // Clip right
309 {
310 std::swap(in, out);
311 nin = nout;
312 nout = 0;
313 const V2T *S = in + nin - 1;
314 T right = aabb.max_corner().x();
315 for (int i = 0; i < nin; ++i) {
316 const V2T &E = in[i];
317 if (E.x() == right) {
318 out[nout++] = E;
319 }
320 else if (E.x() < right) {
321 // E is inside the AABB.
322 if (S->x() > right) {
323 // S is outside the AABB. Calculate an intersection point.
324 T t = (right - S->x()) / (E.x() - S->x());
325 out[nout++] = V2T(right, S->y() + t * (E.y() - S->y()));
326 }
327 out[nout++] = E;
328 }
329 else if (S->x() < right) {
330 // S is inside the AABB, E is outside the AABB.
331 T t = (right - S->x()) / (E.x() - S->x());
332 out[nout++] = V2T(right, S->y() + t * (E.y() - S->y()));
333 }
334 S = &E;
335 }
336 assert(nout <= 8);
337 }
338 // Clip top
339 {
340 std::swap(in, out);
341 nin = nout;
342 nout = 0;
343 const V2T *S = in + nin - 1;
344 T top = aabb.max_corner().y();
345 for (int i = 0; i < nin; ++i) {
346 const V2T &E = in[i];
347 if (E.y() == top) {
348 out[nout++] = E;
349 }
350 else if (E.y() < top) {
351 // E is inside the AABB.
352 if (S->y() > top) {
353 // S is outside the AABB. Calculate an intersection point.
354 T t = (top - S->y()) / (E.y() - S->y());
355 out[nout++] = V2T(S->x() + t * (E.x() - S->x()), top);
356 }
357 out[nout++] = E;
358 }
359 else if (S->y() < top) {
360 // S is inside the AABB, E is outside the AABB.
361 T t = (top - S->y()) / (E.y() - S->y());
362 out[nout++] = V2T(S->x() + t * (E.x() - S->x()), top);
363 }
364 S = &E;
365 }
366 assert(nout <= 8);
367 }
368
369 assert(nout <= 8);
370 return nout;
371 }
372
373 // Calculate area of the circle x AABB intersection.
374 // The calculation is approximate in a way, that the circular segment
375 // intersecting the cell is approximated by its chord (a linear segment).
376 template<typename T>
clip_circle_by_AABB(const boost::geometry::model::d2::point_xy<T> & center,const T radius,const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T>> & aabb,boost::geometry::model::d2::point_xy<T> result[8],bool result_arc[8])377 int clip_circle_by_AABB(
378 const boost::geometry::model::d2::point_xy<T> ¢er,
379 const T radius,
380 const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T> > &aabb,
381 boost::geometry::model::d2::point_xy<T> result[8],
382 bool result_arc[8])
383 {
384 typedef typename V2<T>::Type V2T;
385
386 V2T rect[4] = {
387 aabb.min_corner(),
388 V2T(aabb.max_corner().x(), aabb.min_corner().y()),
389 aabb.max_corner(),
390 V2T(aabb.min_corner().x(), aabb.max_corner().y())
391 };
392
393 int bits_corners = 0;
394 T r2 = sqr(radius);
395 for (int i = 0; i < 4; ++ i, bits_corners <<= 1)
396 bits_corners |= dot(rect[i] - center) >= r2;
397 bits_corners >>= 1;
398
399 if (bits_corners == 0) {
400 // all inside
401 memcpy(result, rect, sizeof(rect));
402 memset(result_arc, true, 4);
403 return 4;
404 }
405
406 if (bits_corners == 0x0f)
407 // all outside
408 return 0;
409
410 // Some corners are outside, some are inside. Trim the rectangle.
411 int n = 0;
412 for (int i = 0; i < 4; ++ i) {
413 bool inside = (bits_corners & 0x08) == 0;
414 bits_corners <<= 1;
415 V2T chordal_points[2];
416 int n_chordal_points = line_circle_intersection(rect[i], rect[(i + 1)%4], center, radius, chordal_points);
417 if (n_chordal_points == 2) {
418 result_arc[n] = true;
419 result[n ++] = chordal_points[0];
420 result_arc[n] = true;
421 result[n ++] = chordal_points[1];
422 } else {
423 if (inside) {
424 result_arc[n] = false;
425 result[n ++] = rect[i];
426 }
427 if (n_chordal_points == 1) {
428 result_arc[n] = false;
429 result[n ++] = chordal_points[0];
430 }
431 }
432 }
433 return n;
434 }
435 /*
436 // Calculate area of the circle x AABB intersection.
437 // The calculation is approximate in a way, that the circular segment
438 // intersecting the cell is approximated by its chord (a linear segment).
439 template<typename T>
440 T circle_AABB_intersection_area(
441 const boost::geometry::model::d2::point_xy<T> ¢er,
442 const T radius,
443 const boost::geometry::model::box<boost::geometry::model::d2::point_xy<T> > &aabb)
444 {
445 typedef typename V2<T>::Type V2T;
446 typedef typename boost::geometry::model::box<V2T> B2T;
447 T radius2 = radius * radius;
448
449 bool intersectionLeft = sqr(aabb.min_corner().x() - center.x()) < radius2;
450 bool intersectionRight = sqr(aabb.max_corner().x() - center.x()) < radius2;
451 bool intersectionBottom = sqr(aabb.min_corner().y() - center.y()) < radius2;
452 bool intersectionTop = sqr(aabb.max_corner().y() - center.y()) < radius2;
453
454 if (! (intersectionLeft || intersectionRight || intersectionTop || intersectionBottom))
455 // No intersection between the aabb and the center.
456 return boost::geometry::point_in_box<V2T, B2T>()::apply(center, aabb) ? 1.f : 0.f;
457
458
459
460 V2T rect[4] = {
461 aabb.min_corner(),
462 V2T(aabb.max_corner().x(), aabb.min_corner().y()),
463 aabb.max_corner(),
464 V2T(aabb.min_corner().x(), aabb.max_corner().y())
465 };
466
467 int bits_corners = 0;
468 T r2 = sqr(radius);
469 for (int i = 0; i < 4; ++ i, bits_corners <<= 1)
470 bits_corners |= dot(rect[i] - center) >= r2;
471 bits_corners >>= 1;
472
473 if (bits_corners == 0) {
474 // all inside
475 memcpy(result, rect, sizeof(rect));
476 memset(result_arc, true, 4);
477 return 4;
478 }
479
480 if (bits_corners == 0x0f)
481 // all outside
482 return 0;
483
484 // Some corners are outside, some are inside. Trim the rectangle.
485 int n = 0;
486 for (int i = 0; i < 4; ++ i) {
487 bool inside = (bits_corners & 0x08) == 0;
488 bits_corners <<= 1;
489 V2T chordal_points[2];
490 int n_chordal_points = line_circle_intersection(rect[i], rect[(i + 1)%4], center, radius, chordal_points);
491 if (n_chordal_points == 2) {
492 result_arc[n] = true;
493 result[n ++] = chordal_points[0];
494 result_arc[n] = true;
495 result[n ++] = chordal_points[1];
496 } else {
497 if (inside) {
498 result_arc[n] = false;
499 result[n ++] = rect[i];
500 }
501 if (n_chordal_points == 1) {
502 result_arc[n] = false;
503 result[n ++] = chordal_points[0];
504 }
505 }
506 }
507 return n;
508 }
509 */
510
511 template<typename T>
polyArea(const boost::geometry::model::d2::point_xy<T> * poly,int n)512 inline T polyArea(const boost::geometry::model::d2::point_xy<T> *poly, int n)
513 {
514 T area = T(0);
515 for (int i = 1; i + 1 < n; ++i)
516 area += cross(poly[i] - poly[0], poly[i + 1] - poly[0]);
517 return T(0.5) * area;
518 }
519
520 template<typename T>
polyCentroid(const boost::geometry::model::d2::point_xy<T> * poly,int n)521 boost::geometry::model::d2::point_xy<T> polyCentroid(const boost::geometry::model::d2::point_xy<T> *poly, int n)
522 {
523 boost::geometry::model::d2::point_xy<T> centroid(T(0), T(0));
524 for (int i = 0; i < n; ++i)
525 centroid += poly[i];
526 return (n == 0) ? centroid : (centroid / float(n));
527 }
528
gcode_paint_layer(const std::vector<V2f> & polyline,float width,float thickness,A2f & acc)529 void gcode_paint_layer(
530 const std::vector<V2f> &polyline,
531 float width,
532 float thickness,
533 A2f &acc)
534 {
535 int nc = acc.shape()[1];
536 int nr = acc.shape()[0];
537 // printf("gcode_paint_layer %d,%d\n", nc, nr);
538 for (size_t iLine = 1; iLine != polyline.size(); ++iLine) {
539 const V2f &p1 = polyline[iLine - 1];
540 const V2f &p2 = polyline[iLine];
541 // printf("p1, p2: %f,%f %f,%f\n", p1.x(), p1.y(), p2.x(), p2.y());
542 const V2f dir = p2 - p1;
543 V2f vperp(- dir.y(), dir.x());
544 vperp = vperp * 0.5f * width / l2(vperp);
545 // Rectangle of the extrusion.
546 V2f rect[4] = { p1 + vperp, p1 - vperp, p2 - vperp, p2 + vperp };
547 // Bounding box of the extrusion.
548 B2f bboxLine(rect[0], rect[0]);
549 boost::geometry::expand(bboxLine, rect[1]);
550 boost::geometry::expand(bboxLine, rect[2]);
551 boost::geometry::expand(bboxLine, rect[3]);
552 B2i bboxLinei(
553 V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))),
554 clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))),
555 V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))),
556 clamp(0, nr-1, int(ceil (bboxLine.max_corner().y())))));
557 // printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y());
558 #ifdef _DEBUG
559 float area = polyArea(rect, 4);
560 assert(area > 0.f);
561 #endif /* _DEBUG */
562 for (int j = bboxLinei.min_corner().y(); j + 1 < bboxLinei.max_corner().y(); ++ j) {
563 for (int i = bboxLinei.min_corner().x(); i + 1 < bboxLinei.max_corner().x(); ++i) {
564 V2f rect2[8];
565 memcpy(rect2, rect, sizeof(rect));
566 int n = clip_rect_by_AABB(rect2, B2f(V2f(float(i), float(j)), V2f(float(i + 1), float(j + 1))));
567 float area = polyArea(rect2, n);
568 assert(area >= 0.f && area <= 1.000001f);
569 acc[j][i] += area * thickness;
570 }
571 }
572 }
573 }
574
gcode_paint_bitmap(const std::vector<V2f> & polyline,float width,A2uc & bitmap,float scale)575 void gcode_paint_bitmap(
576 const std::vector<V2f> &polyline,
577 float width,
578 A2uc &bitmap,
579 float scale)
580 {
581 int nc = bitmap.shape()[1];
582 int nr = bitmap.shape()[0];
583 float r2 = width * width * 0.25f;
584 // printf("gcode_paint_layer %d,%d\n", nc, nr);
585 for (size_t iLine = 1; iLine != polyline.size(); ++iLine) {
586 const V2f &p1 = polyline[iLine - 1];
587 const V2f &p2 = polyline[iLine];
588 // printf("p1, p2: %f,%f %f,%f\n", p1.x(), p1.y(), p2.x(), p2.y());
589 V2f dir = p2 - p1;
590 dir = dir * 0.5f * width / l2(dir);
591 V2f vperp(- dir.y(), dir.x());
592 // Rectangle of the extrusion.
593 V2f rect[4] = { (p1 + vperp - dir) * scale, (p1 - vperp - dir) * scale, (p2 - vperp + dir) * scale, (p2 + vperp + dir) * scale };
594 // Bounding box of the extrusion.
595 B2f bboxLine(rect[0], rect[0]);
596 boost::geometry::expand(bboxLine, rect[1]);
597 boost::geometry::expand(bboxLine, rect[2]);
598 boost::geometry::expand(bboxLine, rect[3]);
599 B2i bboxLinei(
600 V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))),
601 clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))),
602 V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))),
603 clamp(0, nr-1, int(ceil (bboxLine.max_corner().y())))));
604 // printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y());
605 for (int j = bboxLinei.min_corner().y(); j + 1 < bboxLinei.max_corner().y(); ++ j) {
606 for (int i = bboxLinei.min_corner().x(); i + 1 < bboxLinei.max_corner().x(); ++i) {
607 float d2 = dist2_to_line(p1, p2, V2f(float(i) + 0.5f, float(j) + 0.5f) / scale);
608 if (d2 < r2)
609 bitmap[j][i] = 1;
610 }
611 }
612 }
613 }
614
615 struct Cell
616 {
617 // Cell index in the grid.
618 V2i idx;
619 // Total volume of the material stored in this cell.
620 float volume;
621 // Area covered inside this cell, <0,1>.
622 float area;
623 // Fraction of the area covered by the print head. <0,1>
624 float fraction_covered;
625 // Height of the covered part in excess to the expected layer height.
626 float excess_height;
627
operator <Slic3r::Cell628 bool operator<(const Cell &c2) const {
629 return this->excess_height < c2.excess_height;
630 }
631 };
632
633 struct ExtrusionPoint {
634 V2f center;
635 float radius;
636 float height;
637 };
638
639 typedef std::vector<ExtrusionPoint> ExtrusionPoints;
640
gcode_spread_points(A2f & acc,const A2f & mask,const ExtrusionPoints & points,ExtrusionSimulationType simulationType)641 void gcode_spread_points(
642 A2f &acc,
643 const A2f &mask,
644 const ExtrusionPoints &points,
645 ExtrusionSimulationType simulationType)
646 {
647 int nc = acc.shape()[1];
648 int nr = acc.shape()[0];
649
650 // Maximum radius of the spreading points, to allocate a large enough cell array.
651 float rmax = 0.f;
652 for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it)
653 rmax = std::max(rmax, it->radius);
654 size_t n_rows_max = size_t(ceil(rmax * 2.f + 2.f));
655 size_t n_cells_max = sqr(n_rows_max);
656 std::vector<std::pair<float, float> > spans;
657 std::vector<Cell> cells(n_cells_max, Cell());
658 std::vector<float> areas_sum(n_cells_max, 0.f);
659
660 for (ExtrusionPoints::const_iterator it = points.begin(); it != points.end(); ++ it) {
661 const V2f ¢er = it->center;
662 const float radius = it->radius;
663 //const float radius2 = radius * radius;
664 const float height_target = it->height;
665 B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius));
666 B2i bboxi(
667 V2i(clamp(0, nc-1, int(floor(bbox.min_corner().x()))),
668 clamp(0, nr-1, int(floor(bbox.min_corner().y())))),
669 V2i(clamp(0, nc-1, int(ceil (bbox.max_corner().x()))),
670 clamp(0, nr-1, int(ceil (bbox.max_corner().y())))));
671 /*
672 // Fill in the spans, at which the circle intersects the rows.
673 int row_first = bboxi.min_corner().y();
674 int row_last = bboxi.max_corner().y();
675 for (; row_first <= row_last; ++ row_first) {
676 float y = float(j) - center.y();
677 float discr = radius2 - sqr(y);
678 if (discr > 0) {
679 // Circle intersects the row j at 2 points.
680 float d = sqrt(discr);
681 spans.push_back(std.pair<float, float>(center.x() - d, center.x() + d)));
682 break;
683 }
684 }
685 for (int j = row_first + 1; j <= row_last; ++ j) {
686 float y = float(j) - center.y();
687 float discr = radius2 - sqr(y);
688 if (discr > 0) {
689 // Circle intersects the row j at 2 points.
690 float d = sqrt(discr);
691 spans.push_back(std.pair<float, float>(center.x() - d, center.x() + d)));
692 } else {
693 row_last = j - 1;
694 break;
695 }
696 }
697 */
698 float area_total = 0;
699 float volume_total = 0;
700 float volume_excess = 0;
701 float volume_deficit = 0;
702 size_t n_cells = 0;
703 float area_circle_total = 0;
704 #if 0
705 // The intermediate lines.
706 for (int j = row_first; j < row_last; ++ j) {
707 const std::pair<float, float> &span1 = spans[j];
708 const std::pair<float, float> &span2 = spans[j+1];
709 float l1 = span1.first;
710 float l2 = span2.first;
711 float r1 = span1.second;
712 float r2 = span2.second;
713 if (l2 < l1)
714 std::swap(l1, l2);
715 if (r1 > r2)
716 std::swap(r1, r2);
717 int il1 = int(floor(l1));
718 int il2 = int(ceil(l2));
719 int ir1 = int(floor(r1));
720 int ir2 = int(floor(r2));
721 assert(il2 <= ir1);
722 for (int i = il1; i < il2; ++ i) {
723 Cell &cell = cells[n_cells ++];
724 cell.idx.x(i);
725 cell.idx.y(j);
726 cell.area = area;
727 }
728 for (int i = il2; i < ir1; ++ i) {
729 Cell &cell = cells[n_cells ++];
730 cell.idx.x(i);
731 cell.idx.y(j);
732 cell.area = 1.f;
733 }
734 for (int i = ir1; i < ir2; ++ i) {
735 Cell &cell = cells[n_cells ++];
736 cell.idx.x(i);
737 cell.idx.y(j);
738 cell.area = area;
739 }
740 }
741 #else
742 for (int j = bboxi.min_corner().y(); j < bboxi.max_corner().y(); ++ j) {
743 for (int i = bboxi.min_corner().x(); i < bboxi.max_corner().x(); ++i) {
744 B2f bb(V2f(float(i), float(j)), V2f(float(i + 1), float(j + 1)));
745 V2f poly[8];
746 bool poly_arc[8];
747 int n = clip_circle_by_AABB(center, radius, bb, poly, poly_arc);
748 float area = polyArea(poly, n);
749 assert(area >= 0.f && area <= 1.000001f);
750 if (area == 0.f)
751 continue;
752 Cell &cell = cells[n_cells ++];
753 cell.idx.x(i);
754 cell.idx.y(j);
755 cell.volume = acc[j][i];
756 cell.area = mask[j][i];
757 assert(cell.area >= 0.f && cell.area <= 1.000001f);
758 area_circle_total += area;
759 if (cell.area < area)
760 cell.area = area;
761 cell.fraction_covered = clamp(0.f, 1.f, (cell.area > 0) ? (area / cell.area) : 0);
762 if (cell.fraction_covered == 0) {
763 -- n_cells;
764 continue;
765 }
766 float cell_height = cell.volume / cell.area;
767 cell.excess_height = cell_height - height_target;
768 if (cell.excess_height > 0.f)
769 volume_excess += cell.excess_height * cell.area * cell.fraction_covered;
770 else
771 volume_deficit -= cell.excess_height * cell.area * cell.fraction_covered;
772 volume_total += cell.volume * cell.fraction_covered;
773 area_total += cell.area * cell.fraction_covered;
774 }
775 }
776 #endif
777 // float area_circle_total2 = float(M_PI) * sqr(radius);
778 // float area_err = fabs(area_circle_total2 - area_circle_total) / area_circle_total2;
779 // printf("area_circle_total: %f, %f, %f\n", area_circle_total, area_circle_total2, area_err);
780 float volume_full = float(M_PI) * sqr(radius) * height_target;
781 // if (true) {
782 // printf("volume_total: %f, volume_full: %f, fill factor: %f\n", volume_total, volume_full, 100.f - 100.f * volume_total / volume_full);
783 // printf("volume_full: %f, volume_excess+deficit: %f, volume_excess: %f, volume_deficit: %f\n", volume_full, volume_excess+volume_deficit, volume_excess, volume_deficit);
784 if (simulationType == ExtrusionSimulationSpreadFull || volume_total <= volume_full) {
785 // The volume under the circle is spreaded fully.
786 float height_avg = volume_total / area_total;
787 for (size_t i = 0; i < n_cells; ++ i) {
788 const Cell &cell = cells[i];
789 acc[cell.idx.y()][cell.idx.x()] = (1.f - cell.fraction_covered) * cell.volume + cell.fraction_covered * cell.area * height_avg;
790 }
791 } else if (simulationType == ExtrusionSimulationSpreadExcess) {
792 // The volume under the circle does not fit.
793 // 1) Fill the underfilled cells and remove them from the list.
794 float volume_borrowed_total = 0.;
795 for (size_t i = 0; i < n_cells;) {
796 Cell &cell = cells[i];
797 if (cell.excess_height <= 0) {
798 // Fill in the part of the cell below the circle.
799 float volume_borrowed = - cell.excess_height * cell.area * cell.fraction_covered;
800 assert(volume_borrowed >= 0.f);
801 acc[cell.idx.y()][cell.idx.x()] = cell.volume + volume_borrowed;
802 volume_borrowed_total += volume_borrowed;
803 cell = cells[-- n_cells];
804 } else
805 ++ i;
806 }
807 // 2) Sort the remaining cells by their excess height.
808 std::sort(cells.begin(), cells.begin() + n_cells);
809 // 3) Prefix sum the areas per excess height.
810 // The excess height is discrete with the number of excess cells.
811 areas_sum[n_cells-1] = cells[n_cells-1].area * cells[n_cells-1].fraction_covered;
812 for (int i = n_cells - 2; i >= 0; -- i) {
813 const Cell &cell = cells[i];
814 areas_sum[i] = areas_sum[i + 1] + cell.area * cell.fraction_covered;
815 }
816 // 4) Find the excess height, where the volume_excess is over the volume_borrowed_total.
817 float volume_current = 0.f;
818 float excess_height_prev = 0.f;
819 size_t i_top = n_cells;
820 for (size_t i = 0; i < n_cells; ++ i) {
821 const Cell &cell = cells[i];
822 volume_current += (cell.excess_height - excess_height_prev) * areas_sum[i];
823 excess_height_prev = cell.excess_height;
824 if (volume_current > volume_borrowed_total) {
825 i_top = i;
826 break;
827 }
828 }
829 // 5) Remove material from the cells with deficit.
830 // First remove all the excess material from the cells, where the deficit is low.
831 for (size_t i = 0; i < i_top; ++ i) {
832 const Cell &cell = cells[i];
833 float volume_removed = cell.excess_height * cell.area * cell.fraction_covered;
834 acc[cell.idx.y()][cell.idx.x()] = cell.volume - volume_removed;
835 volume_borrowed_total -= volume_removed;
836 }
837 // Second remove some excess material from the cells, where the deficit is high.
838 if (i_top < n_cells) {
839 float height_diff = volume_borrowed_total / areas_sum[i_top];
840 for (size_t i = i_top; i < n_cells; ++ i) {
841 const Cell &cell = cells[i];
842 acc[cell.idx.y()][cell.idx.x()] = cell.volume - height_diff * cell.area * cell.fraction_covered;
843 }
844 }
845 }
846 }
847 }
848
CreatePowerColorGradient24bit()849 inline std::vector<V3uc> CreatePowerColorGradient24bit()
850 {
851 int i;
852 int iColor = 0;
853 std::vector<V3uc> out(6 * 255 + 1, V3uc(0, 0, 0));
854 for (i = 0; i < 256; ++i)
855 out[iColor++] = V3uc(0, 0, i);
856 for (i = 1; i < 256; ++i)
857 out[iColor++] = V3uc(0, i, 255);
858 for (i = 1; i < 256; ++i)
859 out[iColor++] = V3uc(0, 255, 256 - i);
860 for (i = 1; i < 256; ++i)
861 out[iColor++] = V3uc(i, 255, 0);
862 for (i = 1; i < 256; ++i)
863 out[iColor++] = V3uc(255, 256 - i, 0);
864 for (i = 1; i < 256; ++i)
865 out[iColor++] = V3uc(255, 0, i);
866 return out;
867 }
868
869 class ExtrusionSimulatorImpl {
870 public:
871 std::vector<unsigned char> image_data;
872 A2f accumulator;
873 A2uc bitmap;
874 unsigned int bitmap_oversampled;
875 ExtrusionPoints extrusion_points;
876 // RGB gradient to color map the fullness of an accumulator bucket into the output image.
877 std::vector<boost::geometry::model::point<unsigned char, 3, boost::geometry::cs::cartesian> > color_gradient;
878 };
879
ExtrusionSimulator()880 ExtrusionSimulator::ExtrusionSimulator() :
881 pimpl(new ExtrusionSimulatorImpl)
882 {
883 pimpl->color_gradient = CreatePowerColorGradient24bit();
884 pimpl->bitmap_oversampled = 4;
885 }
886
~ExtrusionSimulator()887 ExtrusionSimulator::~ExtrusionSimulator()
888 {
889 delete pimpl;
890 pimpl = NULL;
891 }
892
set_image_size(const Point & image_size)893 void ExtrusionSimulator::set_image_size(const Point &image_size)
894 {
895 // printf("ExtrusionSimulator::set_image_size()\n");
896 if (this->image_size.x() == image_size.x() &&
897 this->image_size.y() == image_size.y())
898 return;
899
900 // printf("Setting image size: %d, %d\n", image_size.x, image_size.y);
901 this->image_size = image_size;
902 // Allocate the image data in an RGBA format.
903 // printf("Allocating image data, size %d\n", image_size.x * image_size.y * 4);
904 pimpl->image_data.assign(image_size.x() * image_size.y() * 4, 0);
905 // printf("Allocating image data, allocated\n");
906
907 //FIXME fill the image with red vertical lines.
908 for (size_t r = 0; r < size_t(image_size.y()); ++ r) {
909 for (size_t c = 0; c < size_t(image_size.x()); c += 2) {
910 // Color red
911 pimpl->image_data[r * image_size.x() * 4 + c * 4] = 255;
912 // Opacity full
913 pimpl->image_data[r * image_size.x() * 4 + c * 4 + 3] = 255;
914 }
915 }
916 // printf("Allocating image data, set\n");
917 }
918
set_viewport(const BoundingBox & viewport)919 void ExtrusionSimulator::set_viewport(const BoundingBox &viewport)
920 {
921 // printf("ExtrusionSimulator::set_viewport(%d, %d, %d, %d)\n", viewport.min.x, viewport.min.y, viewport.max.x, viewport.max.y);
922 if (this->viewport != viewport) {
923 this->viewport = viewport;
924 Point sz = viewport.size();
925 pimpl->accumulator.resize(boost::extents[sz.y()][sz.x()]);
926 pimpl->bitmap.resize(boost::extents[sz.y()*pimpl->bitmap_oversampled][sz.x()*pimpl->bitmap_oversampled]);
927 // printf("Accumulator size: %d, %d\n", sz.y, sz.x);
928 }
929 }
930
set_bounding_box(const BoundingBox & bbox)931 void ExtrusionSimulator::set_bounding_box(const BoundingBox &bbox)
932 {
933 this->bbox = bbox;
934 }
935
image_ptr() const936 const void* ExtrusionSimulator::image_ptr() const
937 {
938 return (pimpl->image_data.empty()) ? NULL : (void*)&pimpl->image_data.front();
939 }
940
reset_accumulator()941 void ExtrusionSimulator::reset_accumulator()
942 {
943 // printf("ExtrusionSimulator::reset_accumulator()\n");
944 Point sz = viewport.size();
945 // printf("Reset accumulator, Accumulator size: %d, %d\n", sz.y, sz.x);
946 memset(&pimpl->accumulator[0][0], 0, sizeof(float) * sz.x() * sz.y());
947 memset(&pimpl->bitmap[0][0], 0, sz.x() * sz.y() * pimpl->bitmap_oversampled * pimpl->bitmap_oversampled);
948 pimpl->extrusion_points.clear();
949 // printf("Reset accumulator, done.\n");
950 }
951
extrude_to_accumulator(const ExtrusionPath & path,const Point & shift,ExtrusionSimulationType simulationType)952 void ExtrusionSimulator::extrude_to_accumulator(const ExtrusionPath &path, const Point &shift, ExtrusionSimulationType simulationType)
953 {
954 // printf("Extruding a path. Nr points: %d, width: %f, height: %f\r\n", path.polyline.points.size(), path.width, path.height);
955 // Convert the path to V2f points, shift and scale them to the viewport.
956 std::vector<V2f> polyline;
957 polyline.reserve(path.polyline.points.size());
958 float scalex = float(viewport.size().x()) / float(bbox.size().x());
959 float scaley = float(viewport.size().y()) / float(bbox.size().y());
960 float w = scale_(path.width) * scalex;
961 //float h = scale_(path.height) * scalex;
962 w = scale_(path.mm3_per_mm / path.height) * scalex;
963 // printf("scalex: %f, scaley: %f\n", scalex, scaley);
964 // printf("bbox: %d,%d %d,%d\n", bbox.min.x(), bbox.min.y, bbox.max.x(), bbox.max.y);
965 for (Points::const_iterator it = path.polyline.points.begin(); it != path.polyline.points.end(); ++ it) {
966 // printf("point %d,%d\n", it->x+shift.x(), it->y+shift.y);
967 ExtrusionPoint ept;
968 ept.center = V2f(float((*it)(0)+shift.x()-bbox.min.x()) * scalex, float((*it)(1)+shift.y()-bbox.min.y()) * scaley);
969 ept.radius = w/2.f;
970 ept.height = 0.5f;
971 polyline.push_back(ept.center);
972 pimpl->extrusion_points.push_back(ept);
973 }
974 // Extrude the polyline into an accumulator.
975 // printf("width scaled: %f, height scaled: %f\n", w, h);
976 gcode_paint_layer(polyline, w, 0.5f, pimpl->accumulator);
977
978 if (simulationType > ExtrusionSimulationDontSpread)
979 gcode_paint_bitmap(polyline, w, pimpl->bitmap, pimpl->bitmap_oversampled);
980 // double path.mm3_per_mm; // mm^3 of plastic per mm of linear head motion
981 // float path.width;
982 // float path.height;
983 }
984
evaluate_accumulator(ExtrusionSimulationType simulationType)985 void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulationType)
986 {
987 // printf("ExtrusionSimulator::evaluate_accumulator()\n");
988 Point sz = viewport.size();
989
990 if (simulationType > ExtrusionSimulationDontSpread) {
991 // Average the cells of a bitmap into a lower resolution floating point mask.
992 A2f mask(boost::extents[sz.y()][sz.x()]);
993 for (int r = 0; r < sz.y(); ++r) {
994 for (int c = 0; c < sz.x(); ++c) {
995 float p = 0;
996 for (unsigned int j = 0; j < pimpl->bitmap_oversampled; ++ j) {
997 for (unsigned int i = 0; i < pimpl->bitmap_oversampled; ++ i) {
998 if (pimpl->bitmap[r * pimpl->bitmap_oversampled + j][c * pimpl->bitmap_oversampled + i])
999 p += 1.f;
1000 }
1001 }
1002 p /= float(pimpl->bitmap_oversampled * pimpl->bitmap_oversampled * 2);
1003 mask[r][c] = p;
1004 }
1005 }
1006
1007 // Spread the excess of the material.
1008 gcode_spread_points(pimpl->accumulator, mask, pimpl->extrusion_points, simulationType);
1009 }
1010
1011 // Color map the accumulator.
1012 for (int r = 0; r < sz.y(); ++r) {
1013 unsigned char *ptr = &pimpl->image_data[(image_size.x() * (viewport.min.y() + r) + viewport.min.x()) * 4];
1014 for (int c = 0; c < sz.x(); ++c) {
1015 #if 1
1016 float p = pimpl->accumulator[r][c];
1017 #else
1018 float p = mask[r][c];
1019 #endif
1020 int idx = int(floor(p * float(pimpl->color_gradient.size()) + 0.5f));
1021 V3uc clr = pimpl->color_gradient[clamp(0, int(pimpl->color_gradient.size()-1), idx)];
1022 *ptr ++ = clr.get<0>();
1023 *ptr ++ = clr.get<1>();
1024 *ptr ++ = clr.get<2>();
1025 *ptr ++ = (idx == 0) ? 0 : 255;
1026 }
1027 }
1028 }
1029
1030 } // namespace Slic3r
1031