From d02a0ec1b27b6877fe523b9510f1d45b4a80922b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= <hejl.lukas@gmail.com>
Date: Mon, 11 Jul 2022 11:31:59 +0200
Subject: [PATCH] Fix of #8447 - Zero spacing when Clipper Z-coordinate (line
 width) equaled zero because the resulting path after clipping had vertices
 from a clipping polygon (which has Z-coordinate equal to zero).

---
 src/libslic3r/PerimeterGenerator.cpp | 47 +++++++++++++++++++++++++++-
 1 file changed, 46 insertions(+), 1 deletion(-)

diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp
index be6c120e0..539679e8d 100644
--- a/src/libslic3r/PerimeterGenerator.cpp
+++ b/src/libslic3r/PerimeterGenerator.cpp
@@ -21,7 +21,8 @@ ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_poly
     
     for (int i = 0; i < (int)lines.size(); ++i) {
         const ThickLine& line = lines[i];
-        
+        assert(line.a_width >= SCALED_EPSILON && line.b_width >= SCALED_EPSILON);
+
         const coordf_t line_len = line.length();
         if (line_len < SCALED_EPSILON) continue;
         
@@ -356,6 +357,50 @@ static ClipperLib_Z::Paths clip_extrusion(const ClipperLib_Z::Path &subject, con
     clipper.Execute(clipType, clipped_polytree, ClipperLib_Z::pftNonZero, ClipperLib_Z::pftNonZero);
     ClipperLib_Z::PolyTreeToPaths(clipped_polytree, clipped_paths);
 
+    // Clipped path could contain vertices from the clip with a Z coordinate equal to zero.
+    // For those vertices, we must assign value based on the subject.
+    // This happens only in sporadic cases.
+    for (ClipperLib_Z::Path &path : clipped_paths)
+        for (ClipperLib_Z::IntPoint &c_pt : path)
+            if (c_pt.z() == 0) {
+                // Now we must find the corresponding line on with this point is located and compute line width (Z coordinate).
+                if (subject.size() <= 2)
+                    continue;
+
+                const Point pt(c_pt.x(), c_pt.y());
+                Point       projected_pt_min;
+                auto        it_min       = subject.begin();
+                auto        dist_sqr_min = std::numeric_limits<double>::max();
+                Point       prev(subject.front().x(), subject.front().y());
+                for (auto it = std::next(subject.begin()); it != subject.end(); ++it) {
+                    Point curr(it->x(), it->y());
+                    Point projected_pt = pt.projection_onto(Line(prev, curr));
+                    if (double dist_sqr = (projected_pt - pt).cast<double>().squaredNorm(); dist_sqr < dist_sqr_min) {
+                        dist_sqr_min     = dist_sqr;
+                        projected_pt_min = projected_pt;
+                        it_min           = std::prev(it);
+                    }
+                    prev = curr;
+                }
+
+                assert(dist_sqr_min <= SCALED_EPSILON);
+                assert(std::next(it_min) != subject.end());
+
+                const Point  pt_a(it_min->x(), it_min->y());
+                const Point  pt_b(std::next(it_min)->x(), std::next(it_min)->y());
+                const double line_len = (pt_b - pt_a).cast<double>().norm();
+                const double dist     = (projected_pt_min - pt_a).cast<double>().norm();
+                c_pt.z()              = coord_t(double(it_min->z()) + (dist / line_len) * double(std::next(it_min)->z() - it_min->z()));
+            }
+
+    assert([&clipped_paths = std::as_const(clipped_paths)]() -> bool {
+        for (const ClipperLib_Z::Path &path : clipped_paths)
+            for (const ClipperLib_Z::IntPoint &pt : path)
+                if (pt.z() <= 0)
+                    return false;
+        return true;
+    }());
+
     return clipped_paths;
 }