2019-11-01 19:59:09 +01:00
#include "clipper/clipper_z.hpp"
#include "libslic3r.h"
2019-11-01 21:13:53 +01:00
#include "ClipperUtils.hpp"
2019-11-01 19:59:09 +01:00
#include "EdgeGrid.hpp"
#include "ExPolygon.hpp"
#include "ElephantFootCompensation.hpp"
#include "Flow.hpp"
#include "Geometry.hpp"
#include "SVG.hpp"
2019-11-15 16:20:01 +01:00
#include "Utils.hpp"
2019-11-01 19:59:09 +01:00
#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 ;
};
2019-11-15 16:20:01 +01:00
// 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().
2019-11-01 19:59:09 +01:00
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 ) {
2019-11-14 17:02:32 +01:00
double tedge = ( dir ( axis ) > 0 ) ? ( double ( bbox . max ( axis )) - SCALED_EPSILON - this -> pt ( axis )) : ( this -> pt ( axis ) - double ( bbox . min ( axis )) - SCALED_EPSILON );
2019-11-01 19:59:09 +01:00
if ( tedge < dx )
2019-11-14 17:02:32 +01:00
t = std :: min ( t , tedge / dx );
2019-11-01 19:59:09 +01:00
}
}
this -> dir = dir ;
if ( t < 1. )
dir *= t ;
this -> pt_end = ( this -> pt + dir ). cast < coord_t > ();
this -> t_min = 1. ;
2019-11-14 17:02:32 +01:00
assert ( this -> grid . bbox (). contains ( this -> pt_start ) && this -> grid . bbox (). contains ( this -> pt_end ));
2019-11-01 19:59:09 +01:00
}
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 ;
2019-11-04 11:26:36 +01:00
assert ( t > - EPSILON && t < 1. + EPSILON );
2019-11-01 19:59:09 +01:00
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 ;
2019-11-04 11:26:36 +01:00
assert ( t2 > - EPSILON && t2 < 1. + EPSILON );
2019-11-01 19:59:09 +01:00
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 ));
2019-11-15 16:20:01 +01:00
svg . draw ( * pt_this , "red" , coord_t ( scale_ ( 0.1 )));
2019-11-01 19:59:09 +01:00
#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 ;
2019-11-15 16:20:01 +01:00
svg . draw ( Point ( pt ), "red" , coord_t ( scale_ ( 0.1 )));
2019-11-01 19:59:09 +01:00
}
#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
2019-11-15 16:20:01 +01:00
printf ( "contour_distance_raycasted-%d-%d.svg - distance %lf \n " , iRun , int ( & pt_next - contour . data ()), unscale < double > ( out . back ()));
2019-11-01 19:59:09 +01:00
#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 ;
}
2019-11-15 16:20:01 +01:00
// 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 ;
}
2019-11-01 19:59:09 +01:00
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 ) {
2019-11-15 16:20:01 +01:00
float prev = prev_value_modulo ( i , compensation );
float next = next_value_modulo ( i , compensation );
2019-11-01 19:59:09 +01:00
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 );
}
}
2019-11-05 10:45:14 +01:00
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 > ();
2019-11-15 16:20:01 +01:00
int j = prev_idx_modulo ( i , contour );
2019-11-05 10:45:14 +01:00
Vec2f pprev = contour [ j ]. cast < float > ();
float prev = compensation [ j ];
float l2 = ( pthis - pprev ). squaredNorm ();
if ( l2 < dist_min2 ) {
float l = sqrt ( l2 );
2019-12-19 09:27:30 +01:00
int jprev = std :: exchange ( j , prev_idx_modulo ( j , contour ));
2019-11-05 10:45:14 +01:00
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 ;
2019-12-19 09:27:30 +01:00
jprev = std :: exchange ( j , prev_idx_modulo ( j , contour ));
2019-11-05 10:45:14 +01:00
}
}
2019-11-15 16:20:01 +01:00
j = next_idx_modulo ( i , contour );
2019-11-05 10:45:14 +01:00
pprev = contour [ j ]. cast < float > ();
float next = compensation [ j ];
l2 = ( pprev - pthis ). squaredNorm ();
if ( l2 < dist_min2 ) {
float l = sqrt ( l2 );
2019-12-19 09:27:30 +01:00
int jprev = std :: exchange ( j , next_idx_modulo ( j , contour ));
2019-11-05 10:45:14 +01:00
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 ;
2019-12-19 09:27:30 +01:00
jprev = std :: exchange ( j , next_idx_modulo ( j , contour ));
2019-11-05 10:45:14 +01:00
}
}
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 );
}
}
2020-02-19 12:57:54 +01:00
#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 */
2020-02-06 14:09:48 +01:00
ExPolygon elephant_foot_compensation ( const ExPolygon & input_expoly , double min_contour_width , const double compensation )
2019-11-14 17:02:32 +01:00
{
2020-02-19 12:57:54 +01:00
assert ( validate_expoly_orientation ( input_expoly ));
2019-11-01 19:59:09 +01:00
double scaled_compensation = scale_ ( compensation );
2020-02-06 14:09:48 +01:00
min_contour_width = scale_ ( min_contour_width );
2019-11-01 19:59:09 +01:00
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 ;
2019-11-14 17:02:32 +01:00
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 ();
2020-02-19 12:57:54 +01:00
assert ( validate_expoly_orientation ( simplified ));
2019-11-14 17:02:32 +01:00
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 );
2020-02-19 12:57:54 +01:00
assert ( poly . is_counter_clockwise () == ( idx_contour == 0 ));
2019-11-15 16:20:01 +01:00
std :: vector < float > dists = contour_distance2 ( grid , idx_contour , poly . points , resampled_point_parameters , scaled_compensation , search_radius );
2019-11-14 17:02:32 +01:00
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 );
2019-11-01 19:59:09 +01:00
}
2019-11-14 17:02:32 +01:00
ExPolygons out_vec = variable_offset_inner_ex ( resampled , deltas , 2. );
if ( out_vec . size () == 1 )
out = std :: move ( out_vec . front ());
2019-11-15 16:20:01 +01:00
else {
2019-11-14 17:02:32 +01:00
// Something went wrong, don't compensate.
out = input_expoly ;
2019-11-15 16:20:01 +01:00
#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 );
}
2019-11-01 19:59:09 +01:00
}
2020-02-14 10:17:55 +01:00
2020-02-19 12:57:54 +01:00
assert ( validate_expoly_orientation ( out ));
2019-11-14 17:02:32 +01:00
return out ;
2019-11-01 19:59:09 +01:00
}
2020-02-06 14:09:48 +01:00
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 );
}
2019-11-01 19:59:09 +01:00
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 ;
}
2020-02-06 14:09:48 +01:00
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 ;
}
2019-11-01 19:59:09 +01:00
} // namespace Slic3r