Add support for structured noise (perlin) fuzzy skin (#7678)

* Add support for perlin noise fuzzy skin

* Support multiple types of coherent noise

* Updated tooltips for more clarity.

* Reorder options as suggested by @discip

* Fix accidental removal of &

* Move libnoise to deps

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
Nick Johnson 2025-01-27 14:45:16 +00:00 committed by GitHub
parent 118e14d788
commit fd0b2547f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 200 additions and 24 deletions

View file

@ -22,6 +22,7 @@
#include "libslic3r/AABBTreeLines.hpp"
#include "Print.hpp"
#include "Algorithm/LineSplit.hpp"
#include "libnoise/noise.h"
static const int overhang_sampling_number = 6;
static const double narrow_loop_length_threshold = 10;
static const double min_degree_gap = 0.1;
@ -44,6 +45,14 @@ static double random_value() {
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; }
};
// Hierarchy of perimeters.
class PerimeterGeneratorLoop {
public:
@ -66,9 +75,39 @@ public:
bool is_internal_contour() const;
};
static std::unique_ptr<noise::module::Module> 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<noise::module::Perlin>(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<noise::module::Billow>(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<noise::module::RidgedMulti>(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<noise::module::Voronoi>(voronoi_noise);
} else {
return std::make_unique<UniformNoise>();
}
}
// Thanks Cura developers for this function.
static void fuzzy_polyline(Points& poly, bool closed, const FuzzySkinConfig& cfg)
static void fuzzy_polyline(Points& poly, bool closed, coordf_t slice_z, const FuzzySkinConfig& cfg)
{
std::unique_ptr<noise::module::Module> 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
@ -90,8 +129,9 @@ static void fuzzy_polyline(Points& poly, bool closed, const FuzzySkinConfig& cfg
for (; p0pa_dist < p0p1_size;
p0pa_dist += min_dist_between_points + random_value() * range_random_point_dist)
{
double r = random_value() * (cfg.thickness * 2.) - cfg.thickness;
out.emplace_back(*p0 + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>());
Point pa = *p0 + (p0p1 * (p0pa_dist / p0p1_size)).cast<coord_t>();
double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
out.emplace_back(pa + (perp(p0p1).cast<double>().normalized() * r).cast<coord_t>());
}
dist_left_over = p0pa_dist - p0p1_size;
p0 = &p1;
@ -108,8 +148,10 @@ static void fuzzy_polyline(Points& poly, bool closed, const FuzzySkinConfig& cfg
}
// Thanks Cura developers for this function.
static void fuzzy_extrusion_line(std::vector<Arachne::ExtrusionJunction>& ext_lines, const FuzzySkinConfig& cfg)
static void fuzzy_extrusion_line(std::vector<Arachne::ExtrusionJunction>& ext_lines, coordf_t slice_z, const FuzzySkinConfig& cfg)
{
std::unique_ptr<noise::module::Module> 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
@ -128,8 +170,9 @@ static void fuzzy_extrusion_line(std::vector<Arachne::ExtrusionJunction>& ext_li
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) {
double r = random_value() * (cfg.thickness * 2.) - cfg.thickness;
out.emplace_back(p0->p + (p0p1 * (p0pa_dist / p0p1_size) + perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index);
Point pa = p0->p + (p0p1 * (p0pa_dist / p0p1_size)).cast<coord_t>();
double r = noise->GetValue(unscale_(pa.x()), unscale_(pa.y()), slice_z) * cfg.thickness;
out.emplace_back(pa + (perp(p0p1).cast<double>().normalized() * r).cast<coord_t>(), p1.w, p1.perimeter_index);
}
dist_left_over = p0pa_dist - p0p1_size;
p0 = &p1;
@ -544,7 +587,7 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
}
fuzzified = loop.polygon;
fuzzy_polyline(fuzzified.points, true, config);
fuzzy_polyline(fuzzified.points, true, perimeter_generator.slice_z, config);
return &fuzzified;
}
@ -589,16 +632,17 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
// 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, r.first);
fuzzy_polyline(fuzzified.points, true, perimeter_generator.slice_z, r.first);
} else {
Points segment;
segment.reserve(splitted.size());
fuzzified.points.clear();
const auto fuzzy_current_segment = [&segment, &fuzzified, &r]() {
const auto slice_z = perimeter_generator.slice_z;
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, r.first);
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();
@ -970,6 +1014,8 @@ static void smooth_overhang_level(ExtrusionPaths &paths)
static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& perimeter_generator, std::vector<PerimeterGeneratorArachneExtrusion>& pg_extrusions,
bool &steep_overhang_contour, bool &steep_overhang_hole)
{
const auto slice_z = perimeter_generator.slice_z;
// Detect steep overhangs
bool overhangs_reverse = perimeter_generator.config->overhang_reverse &&
perimeter_generator.layer_id % 2 == 1; // Only calculate overhang degree on even (from GUI POV) layers
@ -989,7 +1035,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
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, config);
fuzzy_extrusion_line(extrusion.junctions, slice_z, config);
} else {
// Find all affective regions
std::vector<std::pair<const FuzzySkinConfig&, const ExPolygons&>> fuzzified_regions;
@ -1011,19 +1057,19 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p
// 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, r.first);
fuzzy_extrusion_line(extrusion.junctions, slice_z, r.first);
} else {
const auto current_ext = extrusion->junctions;
std::vector<Arachne::ExtrusionJunction> segment;
segment.reserve(current_ext.size());
extrusion->junctions.clear();
const auto fuzzy_current_segment = [&segment, extrusion, &r]() {
extrusion->junctions.push_back(segment.front());
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, r.first);
extrusion->junctions.insert(extrusion->junctions.end(), segment.begin(), segment.end());
extrusion->junctions.push_back(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();
};
@ -1858,7 +1904,11 @@ static void group_region_by_fuzzify(PerimeterGenerator& g)
region_config.fuzzy_skin,
scaled<coord_t>(region_config.fuzzy_skin_thickness.value),
scaled<coord_t>(region_config.fuzzy_skin_point_distance.value),
region_config.fuzzy_skin_first_layer
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) {