#include #include "libslic3r/Algorithm/LineSplit.hpp" #include "libslic3r/Arachne/utils/ExtrusionJunction.hpp" #include "libslic3r/Arachne/utils/ExtrusionLine.hpp" #include "libslic3r/Layer.hpp" #include "libslic3r/PerimeterGenerator.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/Polygon.hpp" #include "libslic3r/Print.hpp" #include "libslic3r/PrintConfig.hpp" #include "FuzzySkin.hpp" #include "libnoise/noise.h" // #define DEBUG_FUZZY using namespace Slic3r; namespace Slic3r::Feature::FuzzySkin { // Produces a random value between 0 and 1. Thread-safe. static double random_value() { thread_local std::random_device rd; // Hash thread ID for random number seed if no hardware rng seed is available thread_local std::mt19937 gen(rd.entropy() > 0 ? rd() : std::hash()(std::this_thread::get_id())); thread_local std::uniform_real_distribution dist(0.0, 1.0); return dist(gen); } class UniformNoise: public noise::module::Module { public: UniformNoise(): Module (GetSourceModuleCount ()) {}; virtual int GetSourceModuleCount() const { return 0; } virtual double GetValue(double x, double y, double z) const { return random_value() * 2 - 1; } }; static std::unique_ptr get_noise_module(const FuzzySkinConfig& cfg) { if (cfg.noise_type == NoiseType::Perlin) { auto perlin_noise = noise::module::Perlin(); perlin_noise.SetFrequency(1 / cfg.noise_scale); perlin_noise.SetOctaveCount(cfg.noise_octaves); perlin_noise.SetPersistence(cfg.noise_persistence); return std::make_unique(perlin_noise); } else if (cfg.noise_type == NoiseType::Billow) { auto billow_noise = noise::module::Billow(); billow_noise.SetFrequency(1 / cfg.noise_scale); billow_noise.SetOctaveCount(cfg.noise_octaves); billow_noise.SetPersistence(cfg.noise_persistence); return std::make_unique(billow_noise); } else if (cfg.noise_type == NoiseType::RidgedMulti) { auto ridged_multi_noise = noise::module::RidgedMulti(); ridged_multi_noise.SetFrequency(1 / cfg.noise_scale); ridged_multi_noise.SetOctaveCount(cfg.noise_octaves); return std::make_unique(ridged_multi_noise); } else if (cfg.noise_type == NoiseType::Voronoi) { auto voronoi_noise = noise::module::Voronoi(); voronoi_noise.SetFrequency(1 / cfg.noise_scale); voronoi_noise.SetDisplacement(1.0); return std::make_unique(voronoi_noise); } else { return std::make_unique(); } } // Thanks Cura developers for this function. void fuzzy_polyline(Points& poly, bool closed, coordf_t slice_z, const FuzzySkinConfig& cfg) { std::unique_ptr noise = get_noise_module(cfg); const double min_dist_between_points = cfg.point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value const double range_random_point_dist = cfg.point_distance / 2.; double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point Point* p0 = &poly.back(); Points out; out.reserve(poly.size()); for (Point &p1 : poly) { if (!closed) { // Skip the first point for open path closed = true; p0 = &p1; continue; } // 'a' is the (next) new point between p0 and p1 Vec2d p0p1 = (p1 - *p0).cast(); double p0p1_size = p0p1.norm(); double p0pa_dist = dist_left_over; for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { Point pa = *p0 + (p0p1 * (p0pa_dist / p0p1_size)).cast(); double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness; out.emplace_back(pa + (perp(p0p1).cast().normalized() * r).cast()); } dist_left_over = p0pa_dist - p0p1_size; p0 = &p1; } while (out.size() < 3) { size_t point_idx = poly.size() - 2; out.emplace_back(poly[point_idx]); if (point_idx == 0) break; -- point_idx; } if (out.size() >= 3) poly = std::move(out); } // Thanks Cura developers for this function. void fuzzy_extrusion_line(Arachne::ExtrusionJunctions& ext_lines, coordf_t slice_z, const FuzzySkinConfig& cfg) { std::unique_ptr noise = get_noise_module(cfg); const double min_dist_between_points = cfg.point_distance * 3. / 4.; // hardcoded: the point distance may vary between 3/4 and 5/4 the supplied value const double range_random_point_dist = cfg.point_distance / 2.; double dist_left_over = random_value() * (min_dist_between_points / 2.); // the distance to be traversed on the line before making the first new point auto* p0 = &ext_lines.front(); Arachne::ExtrusionJunctions out; out.reserve(ext_lines.size()); for (auto& p1 : ext_lines) { if (p0->p == p1.p) { // Connect endpoints. out.emplace_back(p1.p, p1.w, p1.perimeter_index); continue; } // 'a' is the (next) new point between p0 and p1 Vec2d p0p1 = (p1.p - p0->p).cast(); double p0p1_size = p0p1.norm(); double p0pa_dist = dist_left_over; for (; p0pa_dist < p0p1_size; p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist) { Point pa = p0->p + (p0p1 * (p0pa_dist / p0p1_size)).cast(); double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness; out.emplace_back(pa + (perp(p0p1).cast().normalized() * r).cast(), p1.w, p1.perimeter_index); } dist_left_over = p0pa_dist - p0p1_size; p0 = &p1; } while (out.size() < 3) { size_t point_idx = ext_lines.size() - 2; out.emplace_back(ext_lines[point_idx].p, ext_lines[point_idx].w, ext_lines[point_idx].perimeter_index); if (point_idx == 0) break; --point_idx; } if (ext_lines.back().p == ext_lines.front().p) // Connect endpoints. out.front().p = out.back().p; if (out.size() >= 3) ext_lines = std::move(out); } void group_region_by_fuzzify(PerimeterGenerator& g) { g.regions_by_fuzzify.clear(); g.has_fuzzy_skin = false; g.has_fuzzy_hole = false; std::unordered_map regions; for (auto region : *g.compatible_regions) { const auto& region_config = region->region().config(); const FuzzySkinConfig cfg{region_config.fuzzy_skin, scaled(region_config.fuzzy_skin_thickness.value), scaled(region_config.fuzzy_skin_point_distance.value), region_config.fuzzy_skin_first_layer, region_config.fuzzy_skin_noise_type, region_config.fuzzy_skin_scale, region_config.fuzzy_skin_octaves, region_config.fuzzy_skin_persistence}; auto& surfaces = regions[cfg]; for (const auto& surface : region->slices.surfaces) { surfaces.push_back(&surface); } if (cfg.type != FuzzySkinType::None) { g.has_fuzzy_skin = true; if (cfg.type != FuzzySkinType::External) { g.has_fuzzy_hole = true; } } } if (regions.size() == 1) { // optimization g.regions_by_fuzzify[regions.begin()->first] = {}; return; } for (auto& it : regions) { g.regions_by_fuzzify[it.first] = offset_ex(it.second, ClipperSafetyOffset); } } bool should_fuzzify(const FuzzySkinConfig& config, const int layer_id, const size_t loop_idx, const bool is_contour) { const auto fuzziy_type = config.type; if (fuzziy_type == FuzzySkinType::None) { return false; } if (!config.fuzzy_first_layer && layer_id <= 0) { // Do not fuzzy first layer unless told to return false; } const bool fuzzify_contours = loop_idx == 0 || fuzziy_type == FuzzySkinType::AllWalls; const bool fuzzify_holes = fuzzify_contours && (fuzziy_type == FuzzySkinType::All || fuzziy_type == FuzzySkinType::AllWalls); return is_contour ? fuzzify_contours : fuzzify_holes; } Polygon apply_fuzzy_skin(const Polygon& polygon, const PerimeterGenerator& perimeter_generator, const size_t loop_idx, const bool is_contour) { Polygon fuzzified; const auto slice_z = perimeter_generator.slice_z; const auto& regions = perimeter_generator.regions_by_fuzzify; if (regions.size() == 1) { // optimization const auto& config = regions.begin()->first; const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, loop_idx, is_contour); if (!fuzzify) { return polygon; } fuzzified = polygon; fuzzy_polyline(fuzzified.points, true, slice_z, config); return fuzzified; } // Find all affective regions std::vector> fuzzified_regions; fuzzified_regions.reserve(regions.size()); for (const auto& region : regions) { if (should_fuzzify(region.first, perimeter_generator.layer_id, loop_idx, is_contour)) { fuzzified_regions.emplace_back(region.first, region.second); } } if (fuzzified_regions.empty()) { return polygon; } #ifdef DEBUG_FUZZY { int i = 0; for (const auto& r : fuzzified_regions) { BoundingBox bbox = get_extents(perimeter_generator.slices->surfaces); bbox.offset(scale_(1.)); ::Slic3r::SVG svg(debug_out_path("fuzzy_traverse_loops_%d_%d_%d_region_%d.svg", perimeter_generator.layer_id, loop.is_contour ? 0 : 1, loop.depth, i) .c_str(), bbox); svg.draw_outline(perimeter_generator.slices->surfaces); svg.draw_outline(loop.polygon, "green"); svg.draw(r.second, "red", 0.5); svg.draw_outline(r.second, "red"); svg.Close(); i++; } } #endif // Split the loops into lines with different config, and fuzzy them separately fuzzified = polygon; for (const auto& r : fuzzified_regions) { const auto splitted = Algorithm::split_line(fuzzified, r.second, true); if (splitted.empty()) { // No intersection, skip continue; } // Fuzzy splitted polygon if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) { // The entire polygon is fuzzified fuzzy_polyline(fuzzified.points, true, slice_z, r.first); } else { Points segment; segment.reserve(splitted.size()); fuzzified.points.clear(); const auto fuzzy_current_segment = [&segment, &fuzzified, &r, slice_z]() { fuzzified.points.push_back(segment.front()); const auto back = segment.back(); fuzzy_polyline(segment, false, slice_z, r.first); fuzzified.points.insert(fuzzified.points.end(), segment.begin(), segment.end()); fuzzified.points.push_back(back); segment.clear(); }; for (const auto& p : splitted) { if (p.clipped) { segment.push_back(p.p); } else { if (segment.empty()) { fuzzified.points.push_back(p.p); } else { segment.push_back(p.p); fuzzy_current_segment(); } } } if (!segment.empty()) { // Close the loop segment.push_back(splitted.front().p); fuzzy_current_segment(); } } } return fuzzified; } void apply_fuzzy_skin(Arachne::ExtrusionLine* extrusion, const PerimeterGenerator& perimeter_generator, const bool is_contour) { const auto slice_z = perimeter_generator.slice_z; const auto& regions = perimeter_generator.regions_by_fuzzify; if (regions.size() == 1) { // optimization const auto& config = regions.begin()->first; const bool fuzzify = should_fuzzify(config, perimeter_generator.layer_id, extrusion->inset_idx, is_contour); if (fuzzify) fuzzy_extrusion_line(extrusion->junctions, slice_z, config); } else { // Find all affective regions std::vector> fuzzified_regions; fuzzified_regions.reserve(regions.size()); for (const auto& region : regions) { if (should_fuzzify(region.first, perimeter_generator.layer_id, extrusion->inset_idx, is_contour)) { fuzzified_regions.emplace_back(region.first, region.second); } } if (!fuzzified_regions.empty()) { // Split the loops into lines with different config, and fuzzy them separately for (const auto& r : fuzzified_regions) { const auto splitted = Algorithm::split_line(*extrusion, r.second, false); if (splitted.empty()) { // No intersection, skip continue; } // Fuzzy splitted extrusion if (std::all_of(splitted.begin(), splitted.end(), [](const Algorithm::SplitLineJunction& j) { return j.clipped; })) { // The entire polygon is fuzzified fuzzy_extrusion_line(extrusion->junctions, slice_z, r.first); } else { const auto current_ext = extrusion->junctions; std::vector segment; segment.reserve(current_ext.size()); extrusion->junctions.clear(); const auto fuzzy_current_segment = [&segment, &extrusion, &r, slice_z]() { extrusion->junctions.push_back(segment.front()); const auto back = segment.back(); fuzzy_extrusion_line(segment, slice_z, r.first); extrusion->junctions.insert(extrusion->junctions.end(), segment.begin(), segment.end()); extrusion->junctions.push_back(back); segment.clear(); }; const auto to_ex_junction = [¤t_ext](const Algorithm::SplitLineJunction& j) -> Arachne::ExtrusionJunction { Arachne::ExtrusionJunction res = current_ext[j.get_src_index()]; if (!j.is_src()) { res.p = j.p; } return res; }; for (const auto& p : splitted) { if (p.clipped) { segment.push_back(to_ex_junction(p)); } else { if (segment.empty()) { extrusion->junctions.push_back(to_ex_junction(p)); } else { segment.push_back(to_ex_junction(p)); fuzzy_current_segment(); } } } if (!segment.empty()) { fuzzy_current_segment(); } } } } } } } // namespace Slic3r::Feature::FuzzySkin