Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ElephantFootCompensation.cpp « libslic3r « src - github.com/prusa3d/PrusaSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: c111576275ee74ee32489c7bcc1ce54c21112e6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
#include "clipper/clipper_z.hpp"

#include "libslic3r.h"
#include "ClipperUtils.hpp"
#include "EdgeGrid.hpp"
#include "ExPolygon.hpp"
#include "ElephantFootCompensation.hpp"
#include "Flow.hpp"
#include "Geometry.hpp"
#include "SVG.hpp"
#include "Utils.hpp"

#include <cmath>
#include <cassert>

// #define CONTOUR_DISTANCE_DEBUG_SVG

namespace Slic3r {

struct ResampledPoint {
	ResampledPoint(size_t idx_src, bool interpolated, double curve_parameter) : idx_src(idx_src), interpolated(interpolated), curve_parameter(curve_parameter) {}

	size_t		idx_src;
	// Is this point interpolated or initial?
	bool 		interpolated;
	// Euclidean distance along the curve from the 0th point.
	double		curve_parameter;
};

// Distance calculated using SDF (Shape Diameter Function).
// The distance is calculated by casting a fan of rays and measuring the intersection distance.
// Thus the calculation is relatively slow. For the Elephant foot compensation purpose, this distance metric does not avoid 
// pinching off small pieces of a contour, thus this function has been superseded by contour_distance2().
std::vector<float> contour_distance(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double search_radius)
{
	assert(! contour.empty());
	assert(contour.size() >= 2);

	std::vector<float> out;

	if (contour.size() > 2) 
	{
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
		static int iRun = 0;
		++ iRun;
		BoundingBox bbox = get_extents(contour);
		bbox.merge(grid.bbox());
		ExPolygon expoly_grid;
		expoly_grid.contour = Polygon(*grid.contours().front());
		for (size_t i = 1; i < grid.contours().size(); ++ i)
			expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
#endif
		struct Visitor {
			Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_reject) :
				grid(grid), idx_contour(idx_contour), resampled_point_parameters(resampled_point_parameters), dist_same_contour_reject(dist_same_contour_reject) {}

			void init(const size_t aidx_point_start, const Point &apt_start, Vec2d dir, const double radius) {
				this->idx_point_start = aidx_point_start;
				this->pt       = apt_start.cast<double>() + SCALED_EPSILON * dir;
				dir *= radius;
				this->pt_start = this->pt.cast<coord_t>();
				// Trim the vector by the grid's bounding box.
				const BoundingBox &bbox = this->grid.bbox();
				double t = 1.;
				for (size_t axis = 0; axis < 2; ++ axis) {
					double dx = std::abs(dir(axis));
					if (dx >= EPSILON) {
						double tedge = (dir(axis) > 0) ? (double(bbox.max(axis)) - SCALED_EPSILON - this->pt(axis)) : (this->pt(axis) - double(bbox.min(axis)) - SCALED_EPSILON);
						if (tedge < dx)
							t = std::min(t, tedge / dx);
					}
				}
				this->dir      = dir;
				if (t < 1.)
					dir *= t;
				this->pt_end   = (this->pt + dir).cast<coord_t>();
				this->t_min    = 1.;
				assert(this->grid.bbox().contains(this->pt_start) && this->grid.bbox().contains(this->pt_end));
			}

			bool operator()(coord_t iy, coord_t ix) {
				// Called with a row and colum of the grid cell, which is intersected by a line.
				auto cell_data_range = this->grid.cell_data_range(iy, ix);
				bool valid = true;
				for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
					// End points of the line segment and their vector.
					auto segment = this->grid.segment(*it_contour_and_segment);
					if (Geometry::segments_intersect(segment.first, segment.second, this->pt_start, this->pt_end)) {
						// The two segments intersect. Calculate the intersection.
						Vec2d pt2 = segment.first.cast<double>();
						Vec2d dir2 = segment.second.cast<double>() - pt2;
						Vec2d vptpt2 = pt - pt2;
						double denom = dir(0) * dir2(1) - dir2(0) * dir(1);

						if (std::abs(denom) >= EPSILON) {
							double t = cross2(dir2, vptpt2) / denom;
							assert(t > - EPSILON && t < 1. + EPSILON);
							bool this_valid = true;
							if (it_contour_and_segment->first == idx_contour) {
								// The intersected segment originates from the same contour as the starting point.
								// Reject the intersection if it is close to the starting point.
								// Find the start and end points of this segment
								double param_lo = resampled_point_parameters[idx_point_start].curve_parameter;
								double param_hi;
								double param_end = resampled_point_parameters.back().curve_parameter;
								{
									const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first];
									size_t ipt = it_contour_and_segment->second;
									ResampledPoint key(ipt, false, 0.);
									auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
									auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
									assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
									double t2 = cross2(dir, vptpt2) / denom;
									assert(t2 > - EPSILON && t2 < 1. + EPSILON);
									if (++ ipt == ipts.size())
										param_hi = t2 * dir2.norm();
									else
										param_hi = it->curve_parameter + t2 * dir2.norm();
								}
								if (param_lo > param_hi)
									std::swap(param_lo, param_hi);
								assert(param_lo >= 0. && param_lo <= param_end);
								assert(param_hi >= 0. && param_hi <= param_end);
								this_valid = param_hi > param_lo + dist_same_contour_reject && param_hi - param_end < param_lo - dist_same_contour_reject;
							}
							if (t < this->t_min) {
								this->t_min = t;
								valid = this_valid;
							}
						}
					}
					if (! valid)
						this->t_min = 1.;
				}
				// Continue traversing the grid along the edge.
				return true;
			}

			const EdgeGrid::Grid 			   &grid;
			const size_t 		  				idx_contour;
			const std::vector<ResampledPoint>  &resampled_point_parameters;
			const double 						dist_same_contour_reject;

			size_t 								idx_point_start;
			Point			      				pt_start;
			Point				  				pt_end;
			Vec2d				  				pt;
			Vec2d                 				dir;
			// Minium parameter along the vector (pt_end - pt_start).
			double				  				t_min;
		} visitor(grid, idx_contour, resampled_point_parameters, search_radius);

		const Point *pt_this = &contour.back();
		size_t       idx_pt_this = contour.size() - 1;
		const Point *pt_prev = pt_this - 1;
		// perpenduclar vector
		auto		 perp    = [](const Vec2d& v) -> Vec2d { return Vec2d(v.y(), -v.x()); };
		Vec2d        vprev   = (*pt_this - *pt_prev).cast<double>().normalized();
		out.reserve(contour.size() + 1);
		for (const Point &pt_next : contour) {
			Vec2d    vnext   = (pt_next - *pt_this).cast<double>().normalized();
			Vec2d    dir     = - (perp(vprev) + perp(vnext)).normalized();
			Vec2d    dir_perp = perp(dir);
			double   cross   = cross2(vprev, vnext);
			double   dot     = vprev.dot(vnext);
			double   a       = (cross < 0 || dot > 0.5) ? (M_PI / 3.) : (0.48 * acos(std::min(1., - dot)));
			// Throw rays, collect distances.
			std::vector<double> distances;
			int num_rays = 15;

#ifdef CONTOUR_DISTANCE_DEBUG_SVG
			SVG svg(debug_out_path("contour_distance_raycasted-%d-%d.svg", iRun, &pt_next - contour.data()).c_str(), bbox);
			svg.draw(expoly_grid);
			svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
			svg.draw(*pt_this, "red", coord_t(scale_(0.1)));
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */

			for (int i = - num_rays + 1; i < num_rays; ++ i) {
				double angle = a * i / (int)num_rays;
				double c = cos(angle);
				double s = sin(angle);
				Vec2d  v = c * dir + s * dir_perp;
				visitor.init(idx_pt_this, *pt_this, v, search_radius);
				grid.visit_cells_intersecting_line(visitor.pt_start, visitor.pt_end, visitor);
				distances.emplace_back(visitor.t_min);
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
				svg.draw(Line(visitor.pt_start, visitor.pt_end), "yellow", scale_(0.01));
				if (visitor.t_min < 1.) {
					Vec2d pt = visitor.pt + visitor.dir * visitor.t_min;
					svg.draw(Point(pt), "red", coord_t(scale_(0.1)));
				}
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
			}
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
			svg.Close();
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
			std::sort(distances.begin(), distances.end());
#if 0
			double median = distances[distances.size() / 2];
			double standard_deviation = 0;
			for (double d : distances)
				standard_deviation += (d - median) * (d - median);
			standard_deviation = sqrt(standard_deviation / (distances.size() - 1));
			double avg = 0;
			size_t cnt = 0;
			for (double d : distances)
				if (d > median - standard_deviation - EPSILON && d < median + standard_deviation + EPSILON) {
					avg += d;
					++ cnt;
				}
			avg /= double(cnt);
			out.emplace_back(float(avg * search_radius));
#else
			out.emplace_back(float(distances.front() * search_radius));
#endif
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
			printf("contour_distance_raycasted-%d-%d.svg - distance %lf\n", iRun, int(&pt_next - contour.data()), unscale<double>(out.back()));
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
			pt_this = &pt_next;
			idx_pt_this = &pt_next - contour.data();
			vprev   = vnext;
		}
		// Rotate the vector by one item.
		out.emplace_back(out.front());
		out.erase(out.begin());
	}

	return out;
}

// Contour distance by measuring the closest point of an ExPolygon stored inside the EdgeGrid, while filtering out points of the same contour
// at concave regions, or convex regions with low curvature (curvature is estimated as a ratio between contour length and chordal distance crossing the contour ends).
std::vector<float> contour_distance2(const EdgeGrid::Grid &grid, const size_t idx_contour, const Slic3r::Points &contour, const std::vector<ResampledPoint> &resampled_point_parameters, double compensation, double search_radius)
{
	assert(! contour.empty());
	assert(contour.size() >= 2);

	std::vector<float> out;

	if (contour.size() > 2) 
	{
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
		static int iRun = 0;
		++ iRun;
		BoundingBox bbox = get_extents(contour);
		bbox.merge(grid.bbox());
		ExPolygon expoly_grid;
		expoly_grid.contour = Polygon(*grid.contours().front());
		for (size_t i = 1; i < grid.contours().size(); ++ i)
			expoly_grid.holes.emplace_back(Polygon(*grid.contours()[i]));
#endif
		struct Visitor {
			Visitor(const EdgeGrid::Grid &grid, const size_t idx_contour, const std::vector<ResampledPoint> &resampled_point_parameters, double dist_same_contour_accept, double dist_same_contour_reject) :
				grid(grid), idx_contour(idx_contour), contour(*grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {}

			void init(const Points &contour, const Point &apoint) {
				this->idx_point = &apoint - contour.data();
				this->point 	= apoint;
				this->found     = false;
				this->dir_inside = this->dir_inside_at_point(contour, this->idx_point);
			}

			bool operator()(coord_t iy, coord_t ix) {
				// Called with a row and colum of the grid cell, which is intersected by a line.
				auto cell_data_range = this->grid.cell_data_range(iy, ix);
				for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++ it_contour_and_segment) {
					// End points of the line segment and their vector.
					std::pair<const Point&, const Point&> segment = this->grid.segment(*it_contour_and_segment);
				    const Vec2d   v  = (segment.second - segment.first).cast<double>();
				    const Vec2d   va = (this->point - segment.first).cast<double>();
				    const double  l2 = v.squaredNorm();  // avoid a sqrt
				    const double  t  = (l2 == 0.0) ? 0. : clamp(0., 1., va.dot(v) / l2);
				    // Closest point from this->point to the segment.
				    const Vec2d   foot = segment.first.cast<double>() + t * v;
					const Vec2d   bisector = foot - this->point.cast<double>();
					const double  dist = bisector.norm();
				    if ((! this->found || dist < this->distance) && this->dir_inside.dot(bisector) > 0) {
						bool	accept = true;
					    if (it_contour_and_segment->first == idx_contour) {
							// Complex case: The closest segment originates from the same contour as the starting point.
							// Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector (this->pt, foot).
							double param_lo = resampled_point_parameters[this->idx_point].curve_parameter;
							double param_hi;
							double param_end = resampled_point_parameters.back().curve_parameter;
							const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first];
							const size_t		  ipt  = it_contour_and_segment->second;
							{
								ResampledPoint key(ipt, false, 0.);
								auto lower = [](const ResampledPoint& l, const ResampledPoint r) { return l.idx_src < r.idx_src || (l.idx_src == r.idx_src && int(l.interpolated) > int(r.interpolated)); };
								auto it = std::lower_bound(resampled_point_parameters.begin(), resampled_point_parameters.end(), key, lower);
								assert(it != resampled_point_parameters.end() && it->idx_src == ipt && ! it->interpolated);
								param_hi = t * sqrt(l2);
								if (ipt + 1 < ipts.size())
									param_hi += it->curve_parameter;
							}
							if (param_lo > param_hi)
								std::swap(param_lo, param_hi);
							assert(param_lo > - SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON);
							assert(param_hi > - SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON);
							double dist_along_contour = std::min(param_hi - param_lo, param_lo + param_end - param_hi);
							if (dist_along_contour < dist_same_contour_accept)
								accept = false;
							else if (dist < dist_same_contour_reject + SCALED_EPSILON) {
								// this->point is close to foot. This point will only be accepted if the path along the contour is significantly 
								// longer than the bisector. That is, the path shall not bulge away from the bisector too much.
								// Bulge is estimated by 0.6 of the circle circumference drawn around the bisector.
								// Test whether the contour is convex or concave.
								bool inside = 
									(t == 0.) ? this->inside_corner(ipts, ipt, this->point) :
									(t == 1.) ? this->inside_corner(ipts, ipt + 1 == ipts.size() ? 0 : ipt + 1, this->point) :
									this->left_of_segment(ipts, ipt, this->point);
								accept = inside && dist_along_contour > 0.6 * M_PI * dist;
							}
					    }
					    if (accept && (! this->found || dist < this->distance)) {
					    	// Simple case: Just measure the shortest distance.
							this->distance = dist;
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
							this->closest_point = foot.cast<coord_t>();
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
							this->found    = true;
					    }
					}
				}
				// Continue traversing the grid.
				return true;
			}

			const EdgeGrid::Grid 			   &grid;
			const size_t 		  				idx_contour;
			const Points					   &contour;
			const std::vector<ResampledPoint>  &resampled_point_parameters;
			const double                        dist_same_contour_accept;
			const double 						dist_same_contour_reject;

			size_t 								idx_point;
			Point			      				point;
			// Direction inside the contour from idx_point, not normalized.
			Vec2d								dir_inside;
			bool 								found;
			double 								distance;
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
			Point 								closest_point;
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */

		private:
			static Vec2d dir_inside_at_point(const Points &contour, size_t i) {
				size_t iprev = prev_idx_modulo(i, contour);
				size_t inext = next_idx_modulo(i, contour);
				Vec2d v1 = (contour[i] - contour[iprev]).cast<double>();
				Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
				return Vec2d(- v1.y() - v2.y(), v1.x() + v2.x());
			}
			static Vec2d dir_inside_at_segment(const Points& contour, size_t i) {
				size_t inext = next_idx_modulo(i, contour);
				Vec2d v = (contour[inext] - contour[i]).cast<double>();
				return Vec2d(- v.y(), v.x());
			}

			static bool inside_corner(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) {
				const Vec2d pt = pt_oposite.cast<double>();
				size_t iprev = prev_idx_modulo(i, contour);
				size_t inext = next_idx_modulo(i, contour);
				Vec2d v1 = (contour[i] - contour[iprev]).cast<double>();
				Vec2d v2 = (contour[inext] - contour[i]).cast<double>();
				bool  left_of_v1 = cross2(v1, pt - contour[iprev].cast<double>()) > 0.;
				bool  left_of_v2 = cross2(v2, pt - contour[i    ].cast<double>()) > 0.;
				return cross2(v1, v2) > 0 ? 
					left_of_v1 && left_of_v2 : // convex corner
					left_of_v1 || left_of_v2;  // concave corner
			}
			static bool left_of_segment(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) {
				const Vec2d pt = pt_oposite.cast<double>();
				size_t inext = next_idx_modulo(i, contour);
				Vec2d v = (contour[inext] - contour[i]).cast<double>();
				return cross2(v, pt - contour[i].cast<double>()) > 0.;
			}
		} visitor(grid, idx_contour, resampled_point_parameters, 0.5 * compensation * M_PI, search_radius);

		out.reserve(contour.size());
		Point radius_vector(search_radius, search_radius);
		for (const Point &pt : contour) {
			visitor.init(contour, pt);
			grid.visit_cells_intersecting_box(BoundingBox(pt - radius_vector, pt + radius_vector), visitor);
			out.emplace_back(float(visitor.found ? std::min(visitor.distance, search_radius) : search_radius));

#if 0
//#ifdef CONTOUR_DISTANCE_DEBUG_SVG
			if (out.back() < search_radius) {
				SVG svg(debug_out_path("contour_distance_filtered-%d-%d.svg", iRun, int(&pt - contour.data())).c_str(), bbox);
				svg.draw(expoly_grid);
				svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
				svg.draw(pt, "green", coord_t(scale_(0.1)));
				svg.draw(visitor.closest_point, "red", coord_t(scale_(0.1)));
				printf("contour_distance_filtered-%d-%d.svg - distance %lf\n", iRun, int(&pt - contour.data()), unscale<double>(out.back()));
			}
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
		}
#ifdef CONTOUR_DISTANCE_DEBUG_SVG
		if (out.back() < search_radius) {
			SVG svg(debug_out_path("contour_distance_filtered-final-%d.svg", iRun).c_str(), bbox);
			svg.draw(expoly_grid);
			svg.draw_outline(Polygon(contour), "blue", scale_(0.01));
			for (size_t i = 0; i < contour.size(); ++ i)
				svg.draw(contour[i], out[i] < float(search_radius - SCALED_EPSILON) ? "red" : "green", coord_t(scale_(0.1)));
		}
#endif /* CONTOUR_DISTANCE_DEBUG_SVG */
	}

	return out;
}

Points resample_polygon(const Points &contour, double dist, std::vector<ResampledPoint> &resampled_point_parameters)
{
	Points out;
	out.reserve(contour.size());
	resampled_point_parameters.reserve(contour.size());
    if (contour.size() > 2) {
    	Vec2d  pt_prev  = contour.back().cast<double>();
    	for (const Point &pt : contour) {
			size_t idx_this = &pt - contour.data();
    		const Vec2d  pt_this = pt.cast<double>();
    		const Vec2d  v		 = pt_this - pt_prev;
			const double l		 = v.norm();
    		const size_t n		 = size_t(ceil(l / dist));
			const double l_step  = l / n;
    		for (size_t i = 1; i < n; ++ i) {
				double interpolation_parameter = double(i) / n;
    			Vec2d new_pt = pt_prev + v * interpolation_parameter;
    			out.emplace_back(new_pt.cast<coord_t>());
				resampled_point_parameters.emplace_back(idx_this, true, l_step);
			}
    		out.emplace_back(pt);
			resampled_point_parameters.emplace_back(idx_this, false, l_step);
    		pt_prev = pt_this;
    	}
		for (size_t i = 1; i < resampled_point_parameters.size(); ++i)
			resampled_point_parameters[i].curve_parameter += resampled_point_parameters[i - 1].curve_parameter;
    }
    return out;
}

static inline void smooth_compensation(std::vector<float> &compensation, float strength, size_t num_iterations)
{
	std::vector<float> out(compensation);
	for (size_t iter = 0; iter < num_iterations; ++ iter) {
		for (size_t i = 0; i < compensation.size(); ++ i) {
			float prev = prev_value_modulo(i, compensation);
			float next = next_value_modulo(i, compensation);
			float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
			// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
			out[i] = std::max(laplacian, compensation[i]);
		}
		out.swap(compensation);
	}
}

static inline void smooth_compensation_banded(const Points &contour, float band, std::vector<float> &compensation, float strength, size_t num_iterations)
{
	assert(contour.size() == compensation.size());
	assert(contour.size() > 2);
	std::vector<float> out(compensation);
	float dist_min2 = band * band;
	static constexpr bool use_min = false;
	for (size_t iter = 0; iter < num_iterations; ++ iter) {
		for (int i = 0; i < int(compensation.size()); ++ i) {
			const Vec2f  pthis = contour[i].cast<float>();
			
			int		j     = prev_idx_modulo(i, contour);
			Vec2f	pprev = contour[j].cast<float>();
			float	prev  = compensation[j];
			float	l2    = (pthis - pprev).squaredNorm();
			if (l2 < dist_min2) {
				float l = sqrt(l2);
				int jprev = std::exchange(j, prev_idx_modulo(j, contour));
				while (j != i) {
					const Vec2f pp = contour[j].cast<float>();
					const float lthis = (pp - pprev).norm();
					const float lnext = l + lthis;
					if (lnext > band) {
						// Interpolate the compensation value.
						prev = use_min ?
							std::min(prev, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
							lerp(compensation[jprev], compensation[j], (band - l) / lthis);
						break;
					}
					prev  = use_min ? std::min(prev, compensation[j]) : compensation[j];
					pprev = pp;
					l     = lnext;
					jprev = std::exchange(j, prev_idx_modulo(j, contour));
				}
			}

			j = next_idx_modulo(i, contour);
			pprev = contour[j].cast<float>();
			float next = compensation[j];
			l2 = (pprev - pthis).squaredNorm();
			if (l2 < dist_min2) {
				float l = sqrt(l2);
				int jprev = std::exchange(j, next_idx_modulo(j, contour));
				while (j != i) {
					const Vec2f pp = contour[j].cast<float>();
					const float lthis = (pp - pprev).norm();
					const float lnext = l + lthis;
					if (lnext > band) {
						// Interpolate the compensation value.
						next = use_min ?
							std::min(next, lerp(compensation[jprev], compensation[j], (band - l) / lthis)) :
							lerp(compensation[jprev], compensation[j], (band - l) / lthis);
						break;
					}
					next  = use_min ? std::min(next, compensation[j]) : compensation[j];
					pprev = pp;
					l     = lnext;
					jprev = std::exchange(j, next_idx_modulo(j, contour));
				}
			}

			float laplacian = compensation[i] * (1.f - strength) + 0.5f * strength * (prev + next);
			// Compensations are negative. Only apply the laplacian if it leads to lower compensation.
			out[i] = std::max(laplacian, compensation[i]);
		}
		out.swap(compensation);
	}
}

#ifndef NDEBUG
static bool validate_expoly_orientation(const ExPolygon &expoly)
{
	bool valid = expoly.contour.is_counter_clockwise();
    for (auto &h : expoly.holes) 
		valid &= h.is_clockwise();
	return valid;
}
#endif /* NDEBUG */

ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_contour_width, const double compensation)
{	
	assert(validate_expoly_orientation(input_expoly));

	double scaled_compensation = scale_(compensation);
    min_contour_width = scale_(min_contour_width);
	double min_contour_width_compensated = min_contour_width + 2. * scaled_compensation;
	// Make the search radius a bit larger for the averaging in contour_distance over a fan of rays to work.
	double search_radius = min_contour_width_compensated + min_contour_width * 0.5;

	BoundingBox bbox = get_extents(input_expoly.contour);
	Point 		bbox_size = bbox.size();
	ExPolygon   out;
	if (bbox_size.x() < min_contour_width_compensated + SCALED_EPSILON ||
		bbox_size.y() < min_contour_width_compensated + SCALED_EPSILON ||
		input_expoly.area() < min_contour_width_compensated * min_contour_width_compensated * 5.)
	{
		// The contour is tiny. Don't correct it.
		out = input_expoly;
	}
	else
	{
		EdgeGrid::Grid grid;
		ExPolygon simplified = input_expoly.simplify(SCALED_EPSILON).front();
		assert(validate_expoly_orientation(simplified));
		BoundingBox bbox = get_extents(simplified.contour);
		bbox.offset(SCALED_EPSILON);
		grid.set_bbox(bbox);
		grid.create(simplified, coord_t(0.7 * search_radius));
		std::vector<std::vector<float>> deltas;
		deltas.reserve(simplified.holes.size() + 1);
		ExPolygon resampled(simplified);
		double resample_interval = scale_(0.5);
		for (size_t idx_contour = 0; idx_contour <= simplified.holes.size(); ++ idx_contour) {
			Polygon &poly = (idx_contour == 0) ? resampled.contour : resampled.holes[idx_contour - 1];
			std::vector<ResampledPoint> resampled_point_parameters;
			poly.points = resample_polygon(poly.points, resample_interval, resampled_point_parameters);
			assert(poly.is_counter_clockwise() == (idx_contour == 0));
			std::vector<float> dists = contour_distance2(grid, idx_contour, poly.points, resampled_point_parameters, scaled_compensation, search_radius);
			for (float &d : dists) {
	//			printf("Point %d, Distance: %lf\n", int(&d - dists.data()), unscale<double>(d));
				// Convert contour width to available compensation distance.
				if (d < min_contour_width)
					d = 0.f;
				else if (d > min_contour_width_compensated)
					d = - float(scaled_compensation);
				else
					d = - (d - float(min_contour_width)) / 2.f;
				assert(d >= - float(scaled_compensation) && d <= 0.f);
			}
	//		smooth_compensation(dists, 0.4f, 10);
			smooth_compensation_banded(poly.points, float(0.8 * resample_interval), dists, 0.3f, 3);
			deltas.emplace_back(dists);
		}

		ExPolygons out_vec = variable_offset_inner_ex(resampled, deltas, 2.);
		if (out_vec.size() == 1)
			out = std::move(out_vec.front());
		else {
			// Something went wrong, don't compensate.
			out = input_expoly;
#ifdef TESTS_EXPORT_SVGS
			if (out_vec.size() > 1) {
				static int iRun = 0;
				SVG::export_expolygons(debug_out_path("elephant_foot_compensation-many_contours-%d.svg", iRun ++).c_str(),
					{ { { input_expoly },   { "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } },
					  { { out_vec },		{ "gray", "black", "blue", coord_t(scale_(0.02)), 0.5f, "black", coord_t(scale_(0.05)) } } });
			}
#endif /* TESTS_EXPORT_SVGS */
			assert(out_vec.size() == 1);
		}
	}
    
	assert(validate_expoly_orientation(out));
	return out;
}

ExPolygon  elephant_foot_compensation(const ExPolygon  &input, const Flow &external_perimeter_flow, const double compensation)
{
    // The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
    double min_contour_width = double(external_perimeter_flow.width + external_perimeter_flow.spacing());
    return elephant_foot_compensation(input, min_contour_width, compensation);
}

ExPolygons elephant_foot_compensation(const ExPolygons &input, const Flow &external_perimeter_flow, const double compensation)
{
	ExPolygons out;
	out.reserve(input.size());
	for (const ExPolygon &expoly : input)
		out.emplace_back(elephant_foot_compensation(expoly, external_perimeter_flow, compensation));
	return out;
}

ExPolygons elephant_foot_compensation(const ExPolygons &input, double min_contour_width, const double compensation)
{
	ExPolygons out;
	out.reserve(input.size());
	for (const ExPolygon &expoly : input)
		out.emplace_back(elephant_foot_compensation(expoly, min_contour_width, compensation));
	return out;
}

} // namespace Slic3r