From 8e35ece403fc9feabdb886e67bf8c4bc34815b02 Mon Sep 17 00:00:00 2001
From: Filip Sykala - NTB T15p <Filip.Sykala@prusa3d.cz>
Date: Thu, 26 Jan 2023 10:22:27 +0100
Subject: [PATCH] Bevel only tip of spike which ends in duplicit point

---
 src/libslic3r/Emboss.cpp | 173 +++++++++++++++++++++++++++++++++------
 1 file changed, 148 insertions(+), 25 deletions(-)

diff --git a/src/libslic3r/Emboss.cpp b/src/libslic3r/Emboss.cpp
index e07e4f421..9e8f8d1fa 100644
--- a/src/libslic3r/Emboss.cpp
+++ b/src/libslic3r/Emboss.cpp
@@ -75,11 +75,16 @@ struct SpikeDesc
     // Half of Wanted bevel size
     double half_bevel; 
 
-    SpikeDesc(double bevel_size) {
+    /// <summary>
+    /// Calculate spike description
+    /// </summary>
+    /// <param name="bevel_size">Size of spike width after cut of the tip, has to be grater than 2.5</param>
+    /// <param name="pixel_spike_length">When spike has same or more pixels with width less than 1 pixel</param>
+    SpikeDesc(double bevel_size, double pixel_spike_length = 6)
+    {
         // create min angle given by spike_length
         // Use it as minimal height of 1 pixel base spike
-        double length = 6; // has to be smaller than count of heal iteration
-        double angle = 2. * atan2(length, .5); // [rad]
+        double angle  = 2. * atan2(pixel_spike_length, .5); // [rad]
         cos_angle = std::fabs(cos(angle));
 
         // When remove spike this angle is set.
@@ -94,6 +99,10 @@ struct SpikeDesc
 void remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc);
 void remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc);
 void remove_spikes(ExPolygons &expolygons, const SpikeDesc &spike_desc);
+// return TRUE when remove point. It could create polygon with 2 points.
+bool remove_when_spike(Polygon &polygon, size_t index, const SpikeDesc &spike_desc);
+
+void remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &duplicates);
 };
 
 #include <Geometry.hpp>
@@ -165,8 +174,8 @@ void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc)
         bool is_ba_short = ba_size_sq < wanted_size_sq;
         bool is_bc_short = bc_size_sq < wanted_size_sq;
         auto a_side = [&b, &ba_f, &ba_size_sq, &wanted_size]() {
-            Vec2d ab_norm = ba_f / sqrt(ba_size_sq);
-            return b + (wanted_size * ab_norm).cast<coord_t>();
+            Vec2d ba_norm = ba_f / sqrt(ba_size_sq);
+            return b + (wanted_size * ba_norm).cast<coord_t>();
         };
         auto c_side = [&b, &bc_f, &bc_size_sq, &wanted_size]() {
             Vec2d bc_norm = bc_f / sqrt(bc_size_sq);
@@ -234,6 +243,130 @@ void priv::remove_spikes(Polygon &polygon, const SpikeDesc &spike_desc)
     }
 }
 
+bool priv::remove_when_spike(Polygon &polygon, size_t index, const SpikeDesc &spike_desc) {    
+    Points &pts = polygon.points;
+    const Point &a = (index == 0) ? pts.back() : pts[index-1];
+    const Point &b = pts[index];
+    const Point &c = (&b == &pts.back())? pts.front() : pts[index+1];
+
+    // calc sides
+    Vec2d ba = (a - b).cast<double>();
+    Vec2d bc = (c - b).cast<double>();
+
+    double dot_product = ba.dot(bc);
+
+    // sqrt together after multiplication save one sqrt
+    double ba_size_sq = ba.squaredNorm();
+    double bc_size_sq = bc.squaredNorm();
+    double norm       = sqrt(ba_size_sq * bc_size_sq);
+    double cos_angle  = dot_product / norm;
+
+    // small angle are around 1 --> cos(0) = 1
+    if (cos_angle < spike_desc.cos_angle)
+        return false; // not a spike    
+
+    // has to be in range <-1, 1>
+    // Due to preccission of floating point number could be sligtly out of range
+    if (cos_angle > 1.)
+        cos_angle = 1.;
+    //if (cos_angle < -1.)
+    //    cos_angle = -1.;
+
+    // Current Spike angle
+    double angle = acos(cos_angle);
+    double wanted_size    = spike_desc.half_bevel / cos(angle / 2.);
+    double wanted_size_sq = wanted_size * wanted_size;
+
+    bool is_ba_short = ba_size_sq < wanted_size_sq;
+    bool is_bc_short = bc_size_sq < wanted_size_sq;
+
+    auto a_side = [&b, &ba, &ba_size_sq, &wanted_size]() {
+        Vec2d ba_norm = ba / sqrt(ba_size_sq);
+        return b + (wanted_size * ba_norm).cast<coord_t>();
+    };
+    auto c_side = [&b, &bc, &bc_size_sq, &wanted_size]() {
+        Vec2d bc_norm = bc / sqrt(bc_size_sq);
+        return b + (wanted_size * bc_norm).cast<coord_t>();
+    };
+
+    if (is_ba_short && is_bc_short) {
+        // remove short spike
+        pts.erase(pts.begin() + index);
+        return true;
+    } else if (is_ba_short) {
+        // move point B on C-side
+        pts[index] = c_side();
+    } else if (is_bc_short) {
+        // move point B on A-side
+        pts[index] = a_side();
+    } else {
+        // move point B on C-side and add point on A-side(left - before)
+        pts[index] = c_side();
+        Point add = a_side();
+        if (add == pts[index]) {
+            // should be very rare, when SpikeDesc has small base 
+            // will be fixed by remove B point
+            pts.erase(pts.begin() + index);
+            return true;
+        }
+        pts.insert(pts.begin() + index, add);
+    }
+    return false;
+}
+
+void priv::remove_spikes_in_duplicates(ExPolygons &expolygons, const Points &duplicates) { 
+
+    auto check = [](Polygon &polygon, const Point &d) -> bool {
+        double spike_bevel = 1 / SHAPE_SCALE;
+        double spike_length = 5.;
+        const static SpikeDesc sd(spike_bevel, spike_length);
+        Points& pts = polygon.points;
+        bool exist_remove = false;
+        for (size_t i = 0; i < pts.size(); i++) {
+            if (pts[i] != d)
+                continue;
+            exist_remove |= remove_when_spike(polygon, i, sd);
+        }
+        return exist_remove && pts.size() < 3;
+    };
+
+    bool exist_remove = false;
+    for (ExPolygon &expolygon : expolygons) {
+        BoundingBox bb(to_points(expolygon.contour));
+        for (const Point &d : duplicates) {
+            if (!bb.contains(d))
+                continue;
+            exist_remove |= check(expolygon.contour, d);
+            for (Polygon &hole : expolygon.holes)
+                exist_remove |= check(hole, d);
+        }
+    }
+
+    if (exist_remove)
+        remove_bad(expolygons);
+    
+    // erase invalid ExPolygon after removing
+    //expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), 
+    //    [&duplicates, &check](ExPolygon &expolygon) {
+    //    BoundingBox bb(to_points(expolygon.contour));
+    //    for (const Point &d : duplicates) {
+    //        if (!bb.contains(d))
+    //            continue;
+    //        if(check(expolygon.contour, d))
+    //            return true;
+
+    //        // erase invalid hole after removing
+    //        Polygons &holes = expolygon.holes;            
+    //        holes.erase(std::remove_if(holes.begin(), holes.end(),
+    //            [&check, &d](Polygon &hole) { 
+    //                return check(hole, d); 
+    //            })
+    //        );
+    //    }
+    //    return false;
+    //}));
+}
+
 void priv::remove_spikes(Polygons &polygons, const SpikeDesc &spike_desc)
 {
     for (Polygon &polygon : polygons)
@@ -555,15 +688,8 @@ ExPolygons Emboss::heal_shape(const Polygons &shape, double precision)
     ClipperLib::CleanPolygons(paths, clean_distance);
     Polygons polygons = to_polygons(paths);
     polygons.erase(std::remove_if(polygons.begin(), polygons.end(), [](const Polygon &p) { return p.size() < 3; }), polygons.end());
-
-    // use douglas peucker to reduce amount of used points
-    for (Polygon &polygon : polygons)
-        polygon.douglas_peucker(precision);
-
-    priv::SpikeDesc spike(precision);
-    priv::remove_spikes(polygons, spike);
-            
-    // Do not remove all duplicits but do it better way
+                
+    // Do not remove all duplicates but do it better way
     // Overlap all duplicit points by rectangle 3x3
     Points duplicits = collect_duplicates(to_points(polygons));
     if (!duplicits.empty()) {
@@ -580,11 +706,6 @@ ExPolygons Emboss::heal_shape(const Polygons &shape, double precision)
     // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM01/Chap1.html
     ExPolygons res = Slic3r::union_ex(polygons, ClipperLib::pftNonZero);
         
-    priv::remove_spikes(polygons, spike);
-
-    double min_area = precision * precision;
-    priv::remove_small_islands(res, min_area);
-
     heal_shape(res);
     return res;
 }
@@ -638,15 +759,17 @@ bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
         auto it = std::unique(intersections.begin(), intersections.end());
         intersections.erase(it, intersections.end());
 
-        Points duplicits = collect_duplicates(to_points(shape));
-        // duplicits are already uniqua and sorted
+        Points duplicates = collect_duplicates(to_points(shape));
+        // duplicates are already uniqua and sorted
 
         // Check whether shape is already healed
-        if (intersections.empty() && duplicits.empty())
+        if (intersections.empty() && duplicates.empty())
             return true;
 
         assert(holes.empty());
-        holes.reserve(intersections.size() + duplicits.size());
+        holes.reserve(intersections.size() + duplicates.size());
+
+        remove_spikes_in_duplicates(shape, duplicates);
 
         // Fix self intersection in result by subtracting hole 2x2
         for (const Point &p : intersections) {
@@ -656,7 +779,7 @@ bool priv::heal_dupl_inter(ExPolygons &shape, unsigned max_iteration)
         }
 
         // Fix duplicit points by hole 3x3 around duplicit point
-        for (const Point &p : duplicits) {
+        for (const Point &p : duplicates) {
             Polygon hole(priv::pts_3x3);
             hole.translate(p);
             holes.push_back(hole);
@@ -693,7 +816,7 @@ bool priv::heal_dupl_inter2(ExPolygons &shape, unsigned max_iteration) {
         // double minimal_area = 1000;
         // priv::remove_small_islands(shape, minimal_area);
 
-        // check that duplicits and intersections do NOT exists
+        // check that duplicates and intersections do NOT exists
         Points  duplicits       = collect_duplicates(to_points(shape));
         Pointfs intersections_f = intersection_points(shape);
         if (duplicits.empty() && intersections_f.empty())