OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::anonymous_namespace{TextureDisplacement.cpp} Namespace Reference

Classes

struct  DecodedTextureCache
struct  SmoothedTextureCache
struct  AdjustedTextureCache
struct  TextureDetailCache
struct  UnionFind
struct  UnwrapCache
struct  ChartEdge
struct  NetGrid
struct  UVTransform
struct  LscmLookup
struct  PatchRegion
struct  PreparedLayer
struct  Crossing
struct  Edge
struct  StepScan

Typedefs

using Tri2 = std::array<Vec2f, 3>

Functions

void smooth_height_pixels_box (std::vector< uint8_t > &pixels, int width, int height, int radius)
void smooth_height_pixels (std::vector< uint8_t > &pixels, int width, int height, float radius)
float unsharp_radius (int width, int height)
float apply_brightness (float v, float b)
float apply_contrast (float v, float c)
void adjust_channel_spatial (std::vector< uint8_t > &channel, int width, int height, float blur, float sharpness)
float box_face_sign (float n)
Vec2f box_plane_yz (const Vec3f &p, float nx)
Vec2f box_plane_xz (const Vec3f &p, float ny)
Vec2f box_plane_xy (const Vec3f &p, float nz)
Vec2f project_cylindrical (const Vec3f &position, const Vec3f &patch_center, const Vec3f &patch_axis)
Vec2f project_spherical (const Vec3f &position, const Vec3f &patch_center)
indexed_triangle_set compact_patch_with_map (const indexed_triangle_set &patch, std::vector< int > &original_to_compact)
uint64_t undirected_edge_key (int a, int b)
Vec3f face_normal (const indexed_triangle_set &mesh, const stl_triangle_vertex_indices &tri)
bool chart_is_flattenable (const indexed_triangle_set &mesh, const std::vector< Vec3f > &normals, const std::vector< int > &faces)
std::vector< char > split_chart_sides (const indexed_triangle_set &mesh, const std::vector< Vec3f > &normals, const std::vector< int > &faces)
std::vector< int > segment_into_charts (const indexed_triangle_set &mesh, const std::vector< Vec3f > &normals, float seam_angle_deg, const std::unordered_set< uint64_t > &seam_keys, int &chart_count)
std::vector< Vec2f > project_to_tangent_plane (const indexed_triangle_set &chart, const Vec3f &normal)
float area_3d (const indexed_triangle_set &mesh)
float area_2d (const std::vector< Vec2f > &uvs, const std::vector< stl_triangle_vertex_indices > &indices)
uint64_t unwrap_cache_key (const indexed_triangle_set &patch, float seam_angle_deg, float padding_mm, const std::vector< std::pair< int, int > > &seam_edges)
std::map< std::pair< int, int >, std::vector< ChartEdge > > build_shared_edges (const PatchUnwrap &u)
TextureIsland solve_edge_alignment (const Vec2f &rc0, const Vec2f &rc1, const Vec2f &pp0, const Vec2f &pp1, const Vec2f &cen)
bool triangles_overlap (const Tri2 &a, const Tri2 &b, float eps)
Vec3f triplanar_weights (const Vec3f &normal)
std::vector< float > patch_boundary_distance (const indexed_triangle_set &patch, const std::vector< bool > &is_boundary)
void despeckle_triangle_colors (const indexed_triangle_set &mesh, std::vector< int > &color, int passes)
indexed_triangle_set build_texture_displacement_v2 (const indexed_triangle_set &mesh, const std::vector< TextureDisplacementLayer > &layers, const TextureDisplacementFacetsData &facets_data, const TextureDisplacementOptions &options, const DisplacementProgressFn &progress, const TextureColorRequest *color, bool flip_normals, BakeStageRecorder *debug, TextureBakeStats *stats, const std::vector< Vec2f > *model_uv, float resting_drop)
std::shared_ptr< std::vector< PreparedLayer > > prepare_sampleable_layers (const indexed_triangle_set &base_mesh, const std::vector< TextureDisplacementLayer > &layers, const TextureDisplacementFacetsData &facets_data, bool need_color, const std::vector< Vec2f > *model_uv)
void ear_clip (const std::vector< Vec2f > &pts, std::vector< int > poly, const std::function< void(int, int, int)> &emit)
void simplify_polyline (const std::vector< Vec2f > &in, float tol, std::vector< Vec2f > &out)
uint64_t step_edge_key (int a, int b)
StepScan scan_steps (const indexed_triangle_set &mesh, const std::vector< uint8_t > &region, const HeightFieldSampler &sampler, float step_width_mm, float seam_gap_mm, float min_feature_mm, bool nudge)
nlohmann::json vec2_to_json (const Vec2f &v)
nlohmann::json vec3_to_json (const Vec3f &v)
Vec2f vec2_from_json (const nlohmann::json &j, const Vec2f &fallback)
Vec3f vec3_from_json (const nlohmann::json &j, const Vec3f &fallback)
template<class T>
void read (const nlohmann::json &j, const char *k, T &out)
void read_enum (const nlohmann::json &j, const char *k, int &out)

Variables

DecodedTextureCache g_decoded_texture_cache
SmoothedTextureCache g_smoothed_texture_cache
AdjustedTextureCache g_adjusted_texture_cache
constexpr float SOURCE_BLUR_SPAN = 0.05f
constexpr float SOURCE_BLUR_MAX_PX = 48.f
TextureDetailCache g_texture_detail_cache
UnwrapCache g_unwrap_cache
const size_t UNWRAP_CACHE_MAX_ENTRIES = 8

Class Documentation

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::DecodedTextureCache

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::DecodedTextureCache
Collaboration diagram for Slic3r::anonymous_namespace{TextureDisplacement.cpp}::DecodedTextureCache:
Class Members
unordered_map< const void *, pair< weak_ptr< const vector< unsigned char > >, DecodedHeightTexture > > entries
mutex mutex

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::SmoothedTextureCache

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::SmoothedTextureCache
Collaboration diagram for Slic3r::anonymous_namespace{TextureDisplacement.cpp}::SmoothedTextureCache:
Class Members
unordered_map< const void *, Entry > entries
mutex mutex

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::AdjustedTextureCache

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::AdjustedTextureCache
Collaboration diagram for Slic3r::anonymous_namespace{TextureDisplacement.cpp}::AdjustedTextureCache:
Class Members
unordered_map< const void *, Entry > entries
mutex mutex

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::TextureDetailCache

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::TextureDetailCache
Collaboration diagram for Slic3r::anonymous_namespace{TextureDisplacement.cpp}::TextureDetailCache:
Class Members
unordered_map< const void *, pair< weak_ptr< const vector< unsigned char > >, TextureDetail > > entries
mutex mutex

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::UnwrapCache

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::UnwrapCache
Collaboration diagram for Slic3r::anonymous_namespace{TextureDisplacement.cpp}::UnwrapCache:
Class Members
unordered_map< uint64_t, PatchUnwrap > entries
mutex mutex

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::ChartEdge

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::ChartEdge
Class Members
int chart = -1
int uv_hi = -1
int uv_lo = -1

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing
Class Members
int a = -1
int b = -1
float dt_max = std::numeric_limits<float>::max()
int hi = -1
int lo = -1
Vec3f n
int near_a = -1
int near_b = -1
bool no_seam = false
Vec3f p
bool sharp = false
float sin_min = 1.f
int single = -1
float t
float thick = 0.f
bool used = false

◆ Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Edge

struct Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Edge
Collaboration diagram for Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Edge:
Class Members
vector< Crossing > xs

Typedef Documentation

◆ Tri2

using Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Tri2 = std::array<Vec2f, 3>

Function Documentation

◆ adjust_channel_spatial()

void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::adjust_channel_spatial ( std::vector< uint8_t > & channel,
int width,
int height,
float blur,
float sharpness )
470{
471 if (blur > 0.f)
472 smooth_height_pixels(channel, width, height, adjustment_blur_radius(blur, width, height));
473
474 if (sharpness <= 0.f)
475 return;
476
477 // Unsharp mask: push each texel away from its local average by `amount` times the difference. The
478 // blurred reference is a copy, so the sharpen reads the un-sharpened neighbourhood everywhere rather
479 // than its own partly-written output.
480 std::vector<uint8_t> base = channel;
481 smooth_height_pixels(base, width, height, unsharp_radius(width, height));
482 const float amount = 1.5f * std::clamp(sharpness, 0.f, 1.f);
483 tbb::parallel_for(tbb::blocked_range<size_t>(0, channel.size()), [&](const tbb::blocked_range<size_t> &r) {
484 for (size_t i = r.begin(); i < r.end(); ++i) {
485 const float v = float(channel[i]) + amount * (float(channel[i]) - float(base[i]));
486 channel[i] = uint8_t(std::lround(std::clamp(v, 0.f, 255.f)));
487 }
488 });
489}
float unsharp_radius(int width, int height)
Definition TextureDisplacement.cpp:443
void smooth_height_pixels(std::vector< uint8_t > &pixels, int width, int height, float radius)
Definition TextureDisplacement.cpp:257
float adjustment_blur_radius(float blur, int width, int height)
Definition TextureDisplacement.cpp:499

References adjust_channel_spatial(), Slic3r::adjustment_blur_radius(), smooth_height_pixels(), and unsharp_radius().

Referenced by adjust_channel_spatial().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_brightness()

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::apply_brightness ( float v,
float b )
454{
455 return b >= 0.f ? v + b * (1.f - v) : v * (1.f + b);
456}

References apply_brightness().

Referenced by apply_brightness().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ apply_contrast()

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::apply_contrast ( float v,
float c )
461{
462 const float gain = c >= 0.f ? 1.f / std::max(1e-3f, 1.f - c) : 1.f + c;
463 return (v - 0.5f) * gain + 0.5f;
464}

References apply_contrast().

Referenced by apply_contrast().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ area_2d()

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::area_2d ( const std::vector< Vec2f > & uvs,
const std::vector< stl_triangle_vertex_indices > & indices )
1188{
1189 float area = 0.f;
1190 for (const stl_triangle_vertex_indices &t : indices) {
1191 const Vec2f e0 = uvs[size_t(t[1])] - uvs[size_t(t[0])];
1192 const Vec2f e1 = uvs[size_t(t[2])] - uvs[size_t(t[0])];
1193 area += 0.5f * std::abs(e0.x() * e1.y() - e0.y() * e1.x());
1194 }
1195 return area;
1196}
double area(const ExPolygon &poly)
Definition ExPolygon.hpp:511
Eigen::Matrix< float, 2, 1, Eigen::DontAlign > Vec2f
Definition Point.hpp:64

References Slic3r::area(), and area_2d().

Referenced by area_2d().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ area_3d()

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::area_3d ( const indexed_triangle_set & mesh)
1180{
1181 float area = 0.f;
1182 for (const stl_triangle_vertex_indices &t : mesh.indices)
1183 area += 0.5f * (mesh.vertices[t[1]] - mesh.vertices[t[0]]).cross(mesh.vertices[t[2]] - mesh.vertices[t[0]]).norm();
1184 return area;
1185}
T cross(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:168

References Slic3r::area(), area_3d(), and Slic3r::cross().

Referenced by area_3d(), and prepare_sampleable_layers().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ box_face_sign()

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::box_face_sign ( float n)
inline
752{ return n >= 0.f ? 1.f : -1.f; }

References box_face_sign().

Referenced by box_face_sign(), box_plane_xy(), box_plane_xz(), and box_plane_yz().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ box_plane_xy()

Vec2f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::box_plane_xy ( const Vec3f & p,
float nz )
inline
777{ return Vec2f(box_face_sign(nz) * p.x(), -p.y()); }
float box_face_sign(float n)
Definition TextureDisplacement.cpp:752

References box_face_sign(), and box_plane_xy().

Referenced by box_plane_xy().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ box_plane_xz()

Vec2f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::box_plane_xz ( const Vec3f & p,
float ny )
inline
776{ return Vec2f(-box_face_sign(ny) * p.x(), -p.z()); }

References box_face_sign(), and box_plane_xz().

Referenced by box_plane_xz().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ box_plane_yz()

Vec2f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::box_plane_yz ( const Vec3f & p,
float nx )
inline
775{ return Vec2f(box_face_sign(nx) * p.y(), -p.z()); }

References box_face_sign(), and box_plane_yz().

Referenced by box_plane_yz().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ build_shared_edges()

std::map< std::pair< int, int >, std::vector< ChartEdge > > Slic3r::anonymous_namespace{TextureDisplacement.cpp}::build_shared_edges ( const PatchUnwrap & u)
1496{
1497 std::map<std::pair<int, int>, std::vector<ChartEdge>> edges;
1498 for (const auto &[ua, ub] : u.boundary_edges) {
1499 if (ua < 0 || ub < 0 || size_t(ua) >= u.source_vertex.size() || size_t(ub) >= u.source_vertex.size())
1500 continue;
1501 const int ba = u.source_vertex[size_t(ua)], bb = u.source_vertex[size_t(ub)];
1502 if (ba < 0 || bb < 0 || ba == bb)
1503 continue;
1504 const int chart = (size_t(ua) < u.vertex_chart.size()) ? u.vertex_chart[size_t(ua)] : -1;
1505 if (chart < 0)
1506 continue;
1507 const std::pair<int, int> key{ std::min(ba, bb), std::max(ba, bb) };
1508 // Order the uv endpoints to match the base-vertex order in the key.
1509 ChartEdge e{ chart, ua, ub };
1510 if (u.source_vertex[size_t(ua)] != key.first)
1511 std::swap(e.uv_lo, e.uv_hi);
1512 edges[key].push_back(e);
1513 }
1514 return edges;
1515}
std::vector< int > source_vertex
Definition TextureDisplacement.hpp:773
std::vector< int > vertex_chart
Definition TextureDisplacement.hpp:774
std::vector< std::pair< int, int > > boundary_edges
Definition TextureDisplacement.hpp:785

References Slic3r::PatchUnwrap::boundary_edges, build_shared_edges(), Slic3r::PatchUnwrap::source_vertex, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::ChartEdge::uv_hi, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::ChartEdge::uv_lo, and Slic3r::PatchUnwrap::vertex_chart.

Referenced by build_shared_edges().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ build_texture_displacement_v2()

indexed_triangle_set Slic3r::anonymous_namespace{TextureDisplacement.cpp}::build_texture_displacement_v2 ( const indexed_triangle_set & mesh,
const std::vector< TextureDisplacementLayer > & layers,
const TextureDisplacementFacetsData & facets_data,
const TextureDisplacementOptions & options,
const DisplacementProgressFn & progress,
const TextureColorRequest * color,
bool flip_normals,
BakeStageRecorder * debug,
TextureBakeStats * stats,
const std::vector< Vec2f > * model_uv,
float resting_drop )
2729{
2730 HeightFieldSampler combined = make_combined_displacement_sampler(mesh, layers, facets_data, model_uv);
2731 if (!combined)
2732 return mesh; // nothing decodable to displace with
2733
2734 // Unpainted triangles are excluded, keeping them out of refinement and pinned thereafter. The
2735 // paint is finer than that, though: a brush stroke splits a source triangle into pieces, and only
2736 // some of them are painted. `painted_pieces` keeps every layer's painted pieces (they lie in the
2737 // source surface) so the refined faces can be tested against the paint itself, not against the
2738 // source triangle they came from.
2739 std::vector<uint8_t> excluded(mesh.indices.size(), 1);
2740 indexed_triangle_set painted_pieces;
2741 {
2742 const TriangleMesh selector_mesh(mesh);
2743 TriangleSelector selector(selector_mesh);
2744 bool dirty = false;
2745 for (const TriangleSelector::TriangleSplittingData &data : facets_data) {
2746 if (data.triangles_to_split.empty())
2747 continue;
2748 selector.deserialize(data, dirty);
2749 dirty = true;
2750 std::vector<int> piece_src;
2751 const indexed_triangle_set patch =
2752 selector.get_facets_strict(EnforcerBlockerType::ENFORCER, &piece_src);
2753 for (const int src : piece_src)
2754 if (src >= 0 && size_t(src) < excluded.size())
2755 excluded[size_t(src)] = 0;
2756 // `patch` carries the whole mesh's vertex array (see compact_patch_with_map()); append
2757 // only what its pieces reference.
2758 std::vector<int> unused;
2759 const indexed_triangle_set compact = compact_patch_with_map(patch, unused);
2760 const int offset = int(painted_pieces.vertices.size());
2761 painted_pieces.vertices.insert(painted_pieces.vertices.end(), compact.vertices.begin(), compact.vertices.end());
2762 for (const stl_triangle_vertex_indices &t : compact.indices)
2763 painted_pieces.indices.emplace_back(t[0] + offset, t[1] + offset, t[2] + offset);
2764 }
2765 }
2766 if (std::all_of(excluded.begin(), excluded.end(), [](uint8_t e) { return e != 0; }) || painted_pieces.indices.empty())
2767 return mesh; // nothing painted
2768
2769 // Distance to the nearest painted piece. Built once here; the tree is read-only afterwards, so
2770 // the parallel stages below share it freely.
2771 const AABBTreeIndirect::Tree3f painted_tree =
2772 AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(painted_pieces.vertices, painted_pieces.indices);
2773 // `foot`/`normal`, when asked for, are the closest point on the painted pieces and that piece's
2774 // normal. The pieces lie in the *undisplaced* surface, so for a displaced point those two are the
2775 // base position and normal underneath it - the frame colour has to be projected in (see below).
2776 const auto painted_closest = [&painted_pieces, &painted_tree](const Vec3f &p, Vec3f *foot, Vec3f *normal) {
2777 size_t hit = 0;
2778 Vec3f hit_point;
2780 painted_pieces.vertices, painted_pieces.indices, painted_tree, p, hit, hit_point);
2781 if (foot != nullptr)
2782 *foot = hit_point;
2783 if (normal != nullptr && hit < painted_pieces.indices.size()) {
2784 const stl_triangle_vertex_indices &t = painted_pieces.indices[hit];
2785 const Vec3f &a = painted_pieces.vertices[size_t(t[0])], &b = painted_pieces.vertices[size_t(t[1])],
2786 &c = painted_pieces.vertices[size_t(t[2])];
2787 Vec3f n = (b - a).cross(c - a);
2788 const float l = n.norm();
2789 *normal = (l > 0.f) ? Vec3f(n / l) : Vec3f::UnitZ();
2790 }
2791 return d2;
2792 };
2793 const auto painted_dist2 = [&painted_closest](const Vec3f &p) { return painted_closest(p, nullptr, nullptr); };
2794 // Before displacement the queried centroids lie in the same surface as the pieces, so anything
2795 // beyond a hair is genuinely outside the paint.
2796 constexpr float paint_tol = 0.05f;
2797 const auto painted_at = [&painted_dist2](const Vec3f &p) { return painted_dist2(p) < paint_tol * paint_tol; };
2798
2799 // "Auto" resolution and budget (0 and -1) resolve here, from the texture and the model - the mesh
2800 // is already in world mm at this point, so no transform is needed.
2801 const bool auto_edge = options.v2_refine_mm <= 0.f, auto_budget = options.v2_max_triangles_k < 0;
2802 const V2Resolution rec = (auto_edge || auto_budget) ? recommend_v2_resolution(mesh, layers) : V2Resolution{};
2803 TextureBake::PipelineSettings settings;
2804 settings.refine_length = auto_edge ? std::max(0.05, double(rec.edge_mm)) : std::max(0.01f, options.v2_refine_mm);
2805 settings.regularize = options.v2_regularize;
2806 settings.max_triangles = size_t(auto_budget ? std::max(0, rec.budget_k) : options.v2_max_triangles_k) * 1000;
2807 BOOST_LOG_TRIVIAL(info) << "TextureBake resolution: " << settings.refine_length << " mm"
2808 << (auto_edge ? " (auto)" : "") << ", budget " << settings.max_triangles / 1000 << " k"
2809 << (auto_budget ? " (auto)" : "");
2810 // How far a flat-face harvest may move the surface, tied to the resolution: what a bake refined to
2811 // 1.4 mm edges can merge flat is not what one refined to 0.1 mm may touch. A thirty-second of the
2812 // edge is well under a layer either way - at the default resolution it sheds about 5% of the
2813 // triangles on a relief that covers the whole surface, and far more of one with flat ground in it -
2814 // and the floor keeps a very fine resolution from spending the pass for nothing.
2815 settings.harvest_tol = std::clamp(settings.refine_length / 32.0, 0.005, 0.05);
2816 settings.preserve_untextured = true;
2817 // Always on: relief driven under the plate is unprintable whichever pipeline produced it, so this
2818 // is no longer a choice the user has to make. Only geometry that ends up below the model's own
2819 // bottom is moved - downward relief that stays clear of the plate is untouched.
2820 settings.clamp_below_plate = true;
2821 settings.relocate = options.v2_relocate;
2822 settings.flip_edges = options.v2_flip_edges;
2823 // The sampler already returns millimetres, so the displacement stage must not scale it again.
2824 settings.displace.amplitude = 1.f;
2825 settings.displace.symmetric = false;
2826 // The paint decides what moves here, so the angle limits stay off.
2827 settings.displace.bottom_angle_limit = 0.f;
2828 settings.displace.top_angle_limit = 0.f;
2829 // The sampler's normal only picks the projection blend (triplanar weights); the displacement
2830 // direction is the pipeline's own smooth normal. Handing it the Laplacian-smoothed blend normal
2831 // spreads a crease's 50/50 blend over a band of rows instead of one: on a cube edge the single row
2832 // of vertices that samples both faces' patterns half and half otherwise comes out as a row of
2833 // notches, since it matches neither face.
2834 settings.displace.blend_normal_smoothing = 32;
2835 // Refined faces are asked against the paint itself, so a stroke narrower than a source triangle
2836 // moves only what it covers.
2837 // Only when some included source triangle is painted in part: the pieces then cover less area
2838 // than the triangles they came from. Whole-triangle paint (the usual case, and every bench) has
2839 // nothing to gain from a query per refined face.
2840 {
2841 const auto area_of = [](const indexed_triangle_set &its) {
2842 double a = 0.0;
2843 for (const stl_triangle_vertex_indices &t : its.indices)
2844 a += 0.5 * double((its.vertices[size_t(t[1])] - its.vertices[size_t(t[0])])
2845 .cross(its.vertices[size_t(t[2])] - its.vertices[size_t(t[0])]).norm());
2846 return a;
2847 };
2848 double included_area = 0.0;
2849 for (size_t t = 0; t < mesh.indices.size(); ++t)
2850 if (excluded[t] == 0) {
2851 const stl_triangle_vertex_indices &f = mesh.indices[t];
2852 included_area += 0.5 * double((mesh.vertices[size_t(f[1])] - mesh.vertices[size_t(f[0])])
2853 .cross(mesh.vertices[size_t(f[2])] - mesh.vertices[size_t(f[0])]).norm());
2854 }
2855 const double pieces_area = area_of(painted_pieces);
2856 if (pieces_area < included_area * (1.0 - 1e-4))
2857 settings.painted = painted_at;
2858 }
2859 // Graded wherever some of the surface is unpainted. A yes/no to a radius, not a distance: most
2860 // queries are far from the paint, and those the tree turns down at its root.
2861 const bool all_painted = std::none_of(excluded.begin(), excluded.end(), [](uint8_t e) { return e != 0; });
2862 if (settings.painted || !all_painted)
2863 settings.paint_within = [&painted_pieces, &painted_tree](const Vec3d &p, double radius) {
2864 float r2 = float(radius * radius);
2865 return AABBTreeIndirect::is_any_triangle_in_radius(painted_pieces.vertices, painted_pieces.indices,
2866 painted_tree, Vec3f(p.cast<float>()), r2);
2867 };
2868
2869 TextureBake::DisplaceBounds bounds;
2870 bounds.min = bounds.max = mesh.vertices.empty() ? Vec3f::Zero() : mesh.vertices.front();
2871 for (const Vec3f &v : mesh.vertices) {
2872 bounds.min = bounds.min.cwiseMin(v);
2873 bounds.max = bounds.max.cwiseMax(v);
2874 }
2875 // The bottom the pipeline clamps and snaps to is the plane the whole object rests on, not this
2876 // part's own: a part held off the plate by another keeps the relief on its underside.
2877 bounds.min.z() -= resting_drop;
2878
2879 const auto sample = [&combined](const Vec3f &pos, const Vec3f &, const Vec3f &blend_normal) {
2880 // The blend normal chooses the projection; the move itself is along the smooth normal, which
2881 // the pipeline applies on its own.
2882 return combined(pos, blend_normal);
2883 };
2884
2885 // The pipeline takes its displacement direction from the soup's winding, so a mirrored placement
2886 // would drive the whole relief inwards. The paint masks were read off `mesh` above, against its
2887 // own vertex order, so the winding can only be turned round after that - here, on the copy that
2888 // becomes the soup - and has to be turned back on the way out, since the caller undoes the same
2889 // mirror when it maps the result back into the volume's coordinates.
2890 indexed_triangle_set oriented = mesh;
2891 if (flip_normals)
2892 for (stl_triangle_vertex_indices &t : oriented.indices)
2893 std::swap(t[1], t[2]);
2894
2895 // 0 means no simplification, i.e. Bake mode.
2896 const TextureBake::PipelineMode mode = settings.max_triangles > 0 ? TextureBake::PipelineMode::Export
2898 // Colour, when asked for. The sampler is built now so the simplification can see the colour
2899 // boundaries: a simplified triangle must not span two colours, or its one colour is wrong over
2900 // part of it (half a tile in the neighbour's colour, a tile edge that wanders).
2901 const bool want_color = color != nullptr && color->out_triangle != nullptr && bool(color->quantize);
2902 const ColorFieldSampler color_sampler =
2903 want_color ? make_combined_color_sampler(mesh, layers, facets_data, color->quantize, color->quantize_pure, model_uv) :
2905 //
2906 // The *palette* index, not the printed filament. The decimation treats any edge whose two faces
2907 // differ as a crease (TextureBakeDecimate.cpp), so it must only ever see where the **perceived**
2908 // colour changes. A mix is one perceived colour however its components are laid down, which is why
2909 // it has to be the palette index here: back when this was handed a per-triangle interleave instead,
2910 // every band boundary read as a crease, the collapse ran along those lines and left a stack of
2911 // horizontal slivers, and the triangle budget went on drawing a pattern the eye is meant to blend
2912 // away. Faces the paint excludes are skipped by the pipeline itself.
2913 const TextureBake::ColorSampleFn color_sample =
2914 color_sampler ? TextureBake::ColorSampleFn([&color_sampler](const Vec3f &p, const Vec3f &n) {
2915 return color_sampler(p, n);
2916 })
2918 // The pipeline works on `oriented`, whose winding was reversed above for a mirrored placement, so
2919 // the stages it records are wound the same way. Note where they start and turn the whole range
2920 // back afterwards, exactly as the result itself is turned back below.
2921 const size_t debug_mark = (debug != nullptr) ? debug->mark() : 0;
2922 TextureBake::PipelineResult result = TextureBake::run_pipeline(
2923 TextureBake::to_soup(oriented, excluded), sample, settings, bounds, mode, excluded,
2924 [&progress](const char *, double f) {
2925 return !progress || progress(std::clamp(int(f * 100.0), 0, 99));
2926 },
2927 debug, color_sample);
2928 if (debug != nullptr && flip_normals)
2929 debug->rebase(debug_mark, nullptr, /* flip_winding */ true);
2930 if (result.canceled || result.geometry.empty())
2931 return {};
2932
2933 if (stats != nullptr) {
2934 stats->triangles_refined = result.triangles_refined;
2935 stats->triangles_budget = result.triangles_budget;
2936 stats->budget_limited = result.budget_limited;
2937 }
2938 indexed_triangle_set out = TextureBake::to_indexed_triangle_set(result.geometry, &result.face_color);
2939 if (out.indices.empty())
2940 return mesh;
2941 if (flip_normals)
2942 for (stl_triangle_vertex_indices &t : out.indices)
2943 std::swap(t[1], t[2]);
2944
2945 // Colour, per output triangle. The topology is new, so unlike the classic path there is no base
2946 // triangle to inherit a colour from: each output triangle samples the colour stack at its own
2947 // centroid, and takes colour only where the paint is - measured against the painted pieces, which
2948 // an output centroid is never further from than the relief depth. Then the same despeckle and
2949 // filament resolution as the classic path.
2950 if (want_color) {
2951 std::vector<uint8_t> out_color(out.indices.size(), 0);
2952 const ColorFieldSampler &sampler = color_sampler;
2953 if (sampler) {
2954 float max_depth = 0.f;
2955 for (const TextureDisplacementLayer &layer : layers)
2956 max_depth = std::max(max_depth, std::abs(layer.depth_mm));
2957 const float relief_tol = max_depth + paint_tol;
2958 std::vector<int> palette(out.indices.size(), -1);
2959 const std::vector<int> &face_mask = result.face_color;
2960 const bool have_face_mask = face_mask.size() == out.indices.size();
2961 tbb::parallel_for(tbb::blocked_range<size_t>(0, out.indices.size()), [&](const tbb::blocked_range<size_t> &r) {
2962 for (size_t i = r.begin(); i < r.end(); ++i) {
2963 const stl_triangle_vertex_indices &t = out.indices[i];
2964 const Vec3f &a = out.vertices[size_t(t[0])], &b = out.vertices[size_t(t[1])], &c = out.vertices[size_t(t[2])];
2965 const Vec3f centroid = (a + b + c) / 3.f;
2966 // Sample on the *base* surface under this face, not on the relief. The projection
2967 // is a function of position and normal, and the displacement has moved both: the
2968 // triplanar blend weights three axis planes by |n|^4, so a face tilted ~45 degrees
2969 // away from its base normal reads the image half through an unrelated plane. The
2970 // patch border is a ring of exactly such faces - the relief ramps to zero there -
2971 // which is the coloured fringe around the border, and the steep interior slopes
2972 // streak for the same reason. The classic path samples the base patch for this very
2973 // reason; this path was the inconsistent one.
2974 Vec3f foot = centroid, base_n = Vec3f::UnitZ();
2975 const float d2 = painted_closest(centroid, &foot, &base_n);
2976 // Which faces may be coloured comes from the pipeline, which recorded it on the
2977 // refined mesh where the paint mask is exact, and carried it through the collapse,
2978 // the T-junction repair and the weld. Proximity cannot answer this: a displaced face
2979 // is no longer where its base was, so on a part thinner than the relief depth the
2980 // nearest painted surface to the *opposite* face is the painted one, and the texture
2981 // appeared there too. Only the position to sample at still comes from the base
2982 // surface, for the projection reason above.
2983 if (have_face_mask) {
2984 if (face_mask[i] == TextureBake::FACE_UNPAINTED)
2985 continue;
2986 } else if (!all_painted && d2 >= relief_tol * relief_tol) {
2987 continue;
2988 }
2989 palette[i] = sampler(foot, base_n);
2990 }
2991 });
2992 // The despeckle filter is for a fine, uniform mesh, where one facet flipping colour is
2993 // noise. A simplified mesh is neither: its triangles are as large as the colour regions
2994 // themselves and already end on the colour boundaries, so a majority vote among three
2995 // neighbours would repaint whole features. Bake mode (no simplification) keeps it.
2996 // Not "did a collapse run": a flat-face harvest leaves the mesh as fine as it was, and
2997 // reading it as simplified switched the despeckle off for every bake that harvested.
2998 const bool simplified = result.simplified;
2999 despeckle_triangle_colors(out, palette, simplified ? 0 : color->despeckle_passes);
3000 merge_small_color_regions(out, palette, color->min_color_region_mm2);
3001 for (size_t i = 0; i < out.indices.size(); ++i) {
3002 if (palette[i] < 0)
3003 continue;
3004 const stl_triangle_vertex_indices &t = out.indices[i];
3005 const Vec3f &a = out.vertices[size_t(t[0])], &b = out.vertices[size_t(t[1])], &c = out.vertices[size_t(t[2])];
3006 const Vec3f centroid = (a + b + c) / 3.f;
3007 Vec3f normal = (b - a).cross(c - a);
3008 const float nl = normal.norm();
3009 normal = (nl > 0.f) ? Vec3f(normal / nl) : Vec3f::UnitZ();
3010 const int filament = palette[i];
3011 if (filament >= 0)
3012 out_color[i] = uint8_t(std::min(filament + 1, 255));
3013 }
3014 }
3015 *color->out_triangle = std::move(out_color);
3016 }
3017 return out;
3018}
size_t mark() const
Definition TextureBakeDebug.hpp:99
void rebase(size_t from, const Transform3d *to_local, bool flip_winding)
Definition TextureBakeDebug.cpp:143
Definition TriangleMesh.hpp:94
Definition TriangleSelector.hpp:75
Tree< 3, typename VertexType::Scalar > build_aabb_tree_over_indexed_triangle_set(const std::vector< VertexType > &vertices, const std::vector< IndexedFaceType > &faces, const typename VertexType::Scalar eps=0)
Definition AABBTreeIndirect.hpp:677
Tree< 3, float > Tree3f
Definition AABBTreeIndirect.hpp:222
bool is_any_triangle_in_radius(const std::vector< VertexType > &vertices, const std::vector< IndexedFaceType > &faces, const TreeType &tree, const VectorType &point, typename VectorType::Scalar &max_distance_squared)
Definition AABBTreeIndirect.hpp:827
VectorType::Scalar squared_distance_to_indexed_triangle_set(const std::vector< VertexType > &vertices, const std::vector< IndexedFaceType > &faces, const TreeType &tree, const VectorType &point, size_t &hit_idx_out, Eigen::PlainObjectBase< VectorType > &hit_point_out)
Definition AABBTreeIndirect.hpp:801
std::function< int(const Vec3f &centroid, const Vec3f &normal)> ColorSampleFn
Definition TextureBakePipeline.hpp:109
TriSoup to_soup(const indexed_triangle_set &its, const std::vector< uint8_t > &face_excluded)
Definition TextureBakeMesh.cpp:14
indexed_triangle_set to_indexed_triangle_set(const TriSoup &soup, std::vector< int > *face_color)
Definition TextureBakeMesh.cpp:46
PipelineMode
Definition TextureBakePipeline.hpp:39
@ Export
Definition TextureBakePipeline.hpp:43
@ Bake
Definition TextureBakePipeline.hpp:41
PipelineResult run_pipeline(const TriSoup &input, const HeightSampleFn &sample, const PipelineSettings &settings, const DisplaceBounds &bounds, PipelineMode mode, const std::vector< uint8_t > &face_excluded, const PipelineProgressFn &on_progress, BakeStageRecorder *debug, const ColorSampleFn &color_sample)
Definition TextureBakePipeline.cpp:129
void despeckle_triangle_colors(const indexed_triangle_set &mesh, std::vector< int > &color, int passes)
Definition TextureDisplacement.cpp:2571
indexed_triangle_set compact_patch_with_map(const indexed_triangle_set &patch, std::vector< int > &original_to_compact)
Definition TextureDisplacement.cpp:865
std::function< float(const Vec3f &pos, const Vec3f &normal)> HeightFieldSampler
Definition TextureDisplacement.hpp:1104
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition Point.hpp:68
V2Resolution recommend_v2_resolution(const indexed_triangle_set &mesh, const std::vector< TextureDisplacementLayer > &layers, const Transform3d &volume_to_world)
Definition TextureDisplacement.cpp:707
ColorQuantizeFn quantize
Definition TextureDisplacement.hpp:971
std::function< int(const Vec3f &pos, const Vec3f &normal)> ColorFieldSampler
Definition TextureDisplacement.hpp:1120
void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max)
Definition NSVGUtils.cpp:91
ColorQuantizeFn quantize_pure
Definition TextureDisplacement.hpp:975
@ ENFORCER
Definition TriangleSelector.hpp:27
Eigen::Matrix< float, 3, 1, Eigen::DontAlign > Vec3f
Definition Point.hpp:65
bool budget_limited
Definition TextureDisplacement.hpp:1027
std::vector< uint8_t > * out_triangle
Definition TextureDisplacement.hpp:995
Slic3r::Polygons offset(const Slic3r::Polygon &polygon, const float delta, JoinType joinType, double miterLimit)
Definition ClipperUtils.cpp:582
ColorFieldSampler make_combined_color_sampler(const indexed_triangle_set &base_mesh, const std::vector< TextureDisplacementLayer > &layers, const TextureDisplacementFacetsData &facets_data, ColorQuantizeFn quantize, ColorQuantizeFn quantize_pure, const std::vector< Vec2f > *model_uv)
Definition TextureDisplacement.cpp:3840
int despeckle_passes
Definition TextureDisplacement.hpp:983
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124
Point normal(Point pt, double scale)
Definition TreeSupport.cpp:112
size_t triangles_refined
Definition TextureDisplacement.hpp:1021
void merge_small_color_regions(const indexed_triangle_set &mesh, std::vector< int > &color, float min_area_mm2)
Definition TextureDisplacement.cpp:2622
HeightFieldSampler make_combined_displacement_sampler(const indexed_triangle_set &base_mesh, const std::vector< TextureDisplacementLayer > &layers, const TextureDisplacementFacetsData &facets_data, const std::vector< Vec2f > *model_uv)
Definition TextureDisplacement.cpp:3878
float min_color_region_mm2
Definition TextureDisplacement.hpp:988
size_t triangles_budget
Definition TextureDisplacement.hpp:1024
bool v2_flip_edges
Definition TextureDisplacement.hpp:441
bool v2_relocate
Definition TextureDisplacement.hpp:438
int v2_max_triangles_k
Definition TextureDisplacement.hpp:434
bool v2_regularize
Definition TextureDisplacement.hpp:431
float v2_refine_mm
Definition TextureDisplacement.hpp:430
Definition TriangleSelector.hpp:287

References Slic3r::TextureBake::DisplaceSettings::amplitude, Slic3r::TextureBake::Bake, Slic3r::TextureBake::DisplaceSettings::blend_normal_smoothing, Slic3r::TextureBake::DisplaceSettings::bottom_angle_limit, Slic3r::bounds(), Slic3r::V2Resolution::budget_k, Slic3r::TextureBake::PipelineResult::budget_limited, Slic3r::TextureBakeStats::budget_limited, Slic3r::AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(), build_texture_displacement_v2(), Slic3r::TextureBake::PipelineResult::canceled, Slic3r::TextureBake::PipelineSettings::clamp_below_plate, compact_patch_with_map(), Slic3r::cross(), Slic3r::TriangleSelector::deserialize(), Slic3r::TextureColorRequest::despeckle_passes, despeckle_triangle_colors(), Slic3r::TextureBake::PipelineSettings::displace, Slic3r::V2Resolution::edge_mm, Slic3r::TextureBake::TriSoup::empty(), Slic3r::ENFORCER, Slic3r::TextureBake::Export, Slic3r::f(), Slic3r::TextureBake::PipelineResult::face_color, Slic3r::TextureBake::PipelineSettings::flip_edges, Slic3r::TextureBake::PipelineResult::geometry, Slic3r::TriangleSelector::get_facets_strict(), Slic3r::TextureBake::PipelineSettings::harvest_tol, Slic3r::AABBTreeIndirect::is_any_triangle_in_radius(), Slic3r::make_combined_color_sampler(), Slic3r::make_combined_displacement_sampler(), Slic3r::BakeStageRecorder::mark(), Slic3r::TextureBake::PipelineSettings::max_triangles, Slic3r::merge_small_color_regions(), Slic3r::TextureColorRequest::min_color_region_mm2, Slic3r::normal(), Slic3r::offset(), Slic3r::TextureColorRequest::out_triangle, Slic3r::TextureBake::PipelineSettings::paint_within, Slic3r::TextureBake::PipelineSettings::painted, Slic3r::TextureBake::PipelineSettings::preserve_untextured, Slic3r::TextureColorRequest::quantize, Slic3r::TextureColorRequest::quantize_pure, Slic3r::BakeStageRecorder::rebase(), Slic3r::recommend_v2_resolution(), Slic3r::TextureBake::PipelineSettings::refine_length, Slic3r::TextureBake::PipelineSettings::regularize, Slic3r::TextureBake::PipelineSettings::relocate, Slic3r::TextureBake::run_pipeline(), Slic3r::TextureBake::PipelineResult::simplified, Slic3r::AABBTreeIndirect::squared_distance_to_indexed_triangle_set(), Slic3r::TextureBake::DisplaceSettings::symmetric, Slic3r::TextureBake::to_indexed_triangle_set(), Slic3r::TextureBake::to_soup(), Slic3r::TextureBake::DisplaceSettings::top_angle_limit, Slic3r::TextureBake::PipelineResult::triangles_budget, Slic3r::TextureBakeStats::triangles_budget, Slic3r::TextureBake::PipelineResult::triangles_refined, Slic3r::TextureBakeStats::triangles_refined, Slic3r::TextureDisplacementOptions::v2_flip_edges, Slic3r::TextureDisplacementOptions::v2_max_triangles_k, Slic3r::TextureDisplacementOptions::v2_refine_mm, Slic3r::TextureDisplacementOptions::v2_regularize, and Slic3r::TextureDisplacementOptions::v2_relocate.

Referenced by build_texture_displacement_v2().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ chart_is_flattenable()

bool Slic3r::anonymous_namespace{TextureDisplacement.cpp}::chart_is_flattenable ( const indexed_triangle_set & mesh,
const std::vector< Vec3f > & normals,
const std::vector< int > & faces )
930{
931 constexpr float MIN_NORMAL_COS = -0.17f; // cos(100 deg)
932
933 std::unordered_map<uint64_t, int> edge_use;
934 std::unordered_map<int, int> local_vertex;
935 edge_use.reserve(faces.size() * 2);
936 local_vertex.reserve(faces.size());
937 Vec3f normal_sum = Vec3f::Zero();
938 for (const int f : faces) {
939 const stl_triangle_vertex_indices &tri = mesh.indices[size_t(f)];
940 for (int i = 0; i < 3; ++i) {
941 ++edge_use[undirected_edge_key(tri[i], tri[(i + 1) % 3])];
942 local_vertex.emplace(tri[i], int(local_vertex.size()));
943 }
944 normal_sum += normals[size_t(f)];
945 }
946 if (int(local_vertex.size()) - int(edge_use.size()) + int(faces.size()) != 1)
947 return false;
948
949 UnionFind loops(local_vertex.size());
950 int boundary_seed = -1;
951 for (const auto &[key, uses] : edge_use) {
952 if (uses > 2)
953 return false; // non-manifold
954 if (uses == 1) {
955 const int a = local_vertex[int(key >> 32)], b = local_vertex[int(uint32_t(key))];
956 loops.unite(a, b);
957 boundary_seed = a;
958 }
959 }
960 if (boundary_seed < 0)
961 return false; // closed
962 const int loop = loops.find(boundary_seed);
963 for (const auto &[key, uses] : edge_use)
964 if (uses == 1 && loops.find(local_vertex[int(key >> 32)]) != loop)
965 return false; // a second boundary loop: a ring, or a disk with a hole
966
967 const float len = normal_sum.norm();
968 if (len < 1e-6f)
969 return false;
970 const Vec3f mean = normal_sum / len;
971 for (const int f : faces) {
972 const stl_triangle_vertex_indices &tri = mesh.indices[size_t(f)];
973 const bool degenerate = (mesh.vertices[tri[1]] - mesh.vertices[tri[0]]).cross(mesh.vertices[tri[2]] - mesh.vertices[tri[0]])
974 .squaredNorm() < 1e-20f;
975 if (!degenerate && normals[size_t(f)].dot(mean) < MIN_NORMAL_COS)
976 return false;
977 }
978 return true;
979}
bool degenerate(uint32_t vi, uint32_t ti0, uint32_t ti1, const VertexInfo &v_info, const EdgeInfos &e_infos, const Indices &indices)
Definition QuadricEdgeCollapse.cpp:641
uint64_t undirected_edge_key(int a, int b)
Definition TextureDisplacement.cpp:912
T dot(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:154

References chart_is_flattenable(), Slic3r::cross(), Slic3r::dot(), Slic3r::f(), Slic3r::anonymous_namespace{TextureDisplacement.cpp}::UnionFind::find(), undirected_edge_key(), and Slic3r::anonymous_namespace{TextureDisplacement.cpp}::UnionFind::unite().

Referenced by chart_is_flattenable(), and segment_into_charts().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ compact_patch_with_map()

indexed_triangle_set Slic3r::anonymous_namespace{TextureDisplacement.cpp}::compact_patch_with_map ( const indexed_triangle_set & patch,
std::vector< int > & original_to_compact )
866{
867 original_to_compact.assign(patch.vertices.size(), -1);
868 indexed_triangle_set compact;
869 compact.vertices.reserve(patch.vertices.size());
870 compact.indices.reserve(patch.indices.size());
871 for (const stl_triangle_vertex_indices &tri : patch.indices) {
872 stl_triangle_vertex_indices new_tri;
873 for (int i = 0; i < 3; ++i) {
874 const int vi = tri[i];
875 if (original_to_compact[vi] < 0) {
876 original_to_compact[vi] = int(compact.vertices.size());
877 compact.vertices.push_back(patch.vertices[vi]);
878 }
879 new_tri[i] = original_to_compact[vi];
880 }
881 compact.indices.push_back(new_tri);
882 }
883 return compact;
884}
void compact(const VertexInfos &v_infos, const TriangleInfos &t_infos, const EdgeInfos &e_infos, indexed_triangle_set &its)
Definition QuadricEdgeCollapse.cpp:842

References compact_patch_with_map().

Referenced by build_texture_displacement_v2(), and compact_patch_with_map().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ despeckle_triangle_colors()

void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::despeckle_triangle_colors ( const indexed_triangle_set & mesh,
std::vector< int > & color,
int passes )
2572{
2573 if (passes <= 0 || color.size() != mesh.indices.size())
2574 return;
2575 const std::vector<Vec3i32> neighbors = its_face_neighbors(mesh);
2576 if (neighbors.size() != mesh.indices.size())
2577 return;
2578
2579 std::vector<int> prev;
2580 for (int pass = 0; pass < passes; ++pass) {
2581 prev = color;
2582 tbb::parallel_for(tbb::blocked_range<size_t>(0, color.size()),
2583 [&](const tbb::blocked_range<size_t> &range) {
2584 for (size_t i = range.begin(); i < range.end(); ++i) {
2585 if (prev[i] < 0)
2586 continue;
2587 // At most four candidates (self plus three neighbours), so counting by a linear scan
2588 // is cheaper than any map.
2589 int cand[4] = { prev[i], -1, -1, -1 };
2590 int count[4] = { 1, 0, 0, 0 };
2591 int n = 1;
2592 for (int e = 0; e < 3; ++e) {
2593 const int nb = neighbors[i][e];
2594 if (nb < 0 || size_t(nb) >= prev.size() || prev[size_t(nb)] < 0)
2595 continue;
2596 const int c = prev[size_t(nb)];
2597 int k = 0;
2598 for (; k < n; ++k)
2599 if (cand[k] == c) {
2600 ++count[k];
2601 break;
2602 }
2603 if (k == n && n < 4) {
2604 cand[n] = c;
2605 count[n] = 1;
2606 ++n;
2607 }
2608 }
2609 // Strictly greater, so a tie leaves the triangle alone.
2610 int best = 0;
2611 for (int k = 1; k < n; ++k)
2612 if (count[k] > count[best])
2613 best = k;
2614 if (count[best] > count[0])
2615 color[i] = cand[best];
2616 }
2617 });
2618 }
2619}
std::vector< Vec3i32 > its_face_neighbors(const indexed_triangle_set &its)
Definition TriangleMesh.cpp:1615
auto range(Cont &&cont)
Definition libslic3r.h:405

References despeckle_triangle_colors(), Slic3r::its_face_neighbors(), and Slic3r::range().

Referenced by build_texture_displacement_v2(), and despeckle_triangle_colors().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ ear_clip()

void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::ear_clip ( const std::vector< Vec2f > & pts,
std::vector< int > poly,
const std::function< void(int, int, int)> & emit )
4563{
4564 auto cross2 = [](const Vec2f &o, const Vec2f &a, const Vec2f &b) {
4565 return (a.x() - o.x()) * (b.y() - o.y()) - (a.y() - o.y()) * (b.x() - o.x());
4566 };
4567 // A corner flatter than this is collinear: several vertices along one edge of the triangle. Such
4568 // a corner is never an ear - the zero-area triangle would lie on the edge, where the neighbour,
4569 // seeing the same points from its side, may emit the same one.
4570 float flat = 0.f;
4571 {
4572 Vec2f lo = pts[size_t(poly[0])], hi = lo;
4573 for (int id : poly) { lo = lo.cwiseMin(pts[size_t(id)]); hi = hi.cwiseMax(pts[size_t(id)]); }
4574 flat = 1e-5f * (hi - lo).squaredNorm();
4575 }
4576 while (poly.size() > 3) {
4577 const size_t n = poly.size();
4578 bool found = false;
4579 for (size_t i = 0; i < n && !found; ++i) {
4580 const int ia = poly[(i + n - 1) % n], ib = poly[i], ic = poly[(i + 1) % n];
4581 const Vec2f &a = pts[size_t(ia)], &b = pts[size_t(ib)], &c = pts[size_t(ic)];
4582 const float area2 = cross2(a, b, c);
4583 if (area2 <= flat)
4584 continue; // reflex or flat corner
4585 const float eps = 1e-6f * area2;
4586 bool clean = true;
4587 size_t on_base = 0;
4588 for (size_t j = 0; j < n && clean; ++j) {
4589 const int ij = poly[j];
4590 if (ij == ia || ij == ib || ij == ic)
4591 continue;
4592 const Vec2f &p = pts[size_t(ij)];
4593 clean = !(cross2(a, b, p) > eps && cross2(b, c, p) > eps && cross2(c, a, p) > eps);
4594 // On the ear's base, between its ends: a run of vertices along the base, as when
4595 // several sit on one edge of the triangle.
4596 if (std::abs(cross2(c, a, p)) <= flat && (p - c).dot(a - c) > 0.f && (p - a).dot(c - a) > 0.f)
4597 ++on_base;
4598 }
4599 if (!clean)
4600 continue;
4601 if (on_base > 0) {
4602 // The base carries other vertices. When those are all that is left of the polygon,
4603 // in order from c round to a, fan them from b and finish; otherwise the ear is not
4604 // clear of the rest of the polygon.
4605 if (on_base + 3 != n)
4606 continue;
4607 for (size_t j = 0; j + 1 < n - 1; ++j) {
4608 const int u = poly[(i + 1 + j) % n], v = poly[(i + 2 + j) % n];
4609 emit(ib, u, v);
4610 }
4611 poly.clear();
4612 return;
4613 }
4614 emit(ia, ib, ic);
4615 poly.erase(poly.begin() + long(i));
4616 found = true;
4617 }
4618 if (!found) {
4619 // Clip the smallest convex corner anyway, or failing that the flattest: a thin triangle
4620 // keeps every edge accounted for, and a convex one at least stays on the polygon's side.
4621 size_t pick = 0;
4622 float best = std::numeric_limits<float>::max();
4623 bool convex_found = false;
4624 for (size_t i = 0; i < n; ++i) {
4625 const Vec2f &a = pts[size_t(poly[(i + n - 1) % n])], &b = pts[size_t(poly[i])], &c = pts[size_t(poly[(i + 1) % n])];
4626 const float m = cross2(a, b, c);
4627 if (m > flat && (!convex_found || m < best)) { best = m; pick = i; convex_found = true; }
4628 else if (!convex_found && std::abs(m) < best) { best = std::abs(m); pick = i; }
4629 }
4630 emit(poly[(pick + n - 1) % n], poly[pick], poly[(pick + 1) % n]);
4631 poly.erase(poly.begin() + long(pick));
4632 }
4633 }
4634 if (poly.size() == 3)
4635 emit(poly[0], poly[1], poly[2]);
4636}
void poly(std::vector< std::pair< Vec2d, Vec2d > > &out, std::initializer_list< Vec2d > p)
Definition DesignSketchTool.cpp:7751
int32_t cross2(const Eigen::MatrixBase< Eigen::Matrix< int32_t, 2, 1, Options > > &v1, const Eigen::MatrixBase< Eigen::Matrix< int32_t, 2, 1, Options > > &v2)=delete

References Slic3r::cross2(), Slic3r::dot(), and ear_clip().

Referenced by ear_clip().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ face_normal()

Vec3f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::face_normal ( const indexed_triangle_set & mesh,
const stl_triangle_vertex_indices & tri )
920{
921 const Vec3f n = (mesh.vertices[tri[1]] - mesh.vertices[tri[0]]).cross(mesh.vertices[tri[2]] - mesh.vertices[tri[0]]);
922 const float len = n.norm();
923 return (len > 1e-12f) ? Vec3f(n / len) : Vec3f::UnitZ();
924}

References Slic3r::cross(), and face_normal().

Referenced by face_normal().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ patch_boundary_distance()

std::vector< float > Slic3r::anonymous_namespace{TextureDisplacement.cpp}::patch_boundary_distance ( const indexed_triangle_set & patch,
const std::vector< bool > & is_boundary )
2525{
2526 const size_t n = patch.vertices.size();
2527 std::vector<std::vector<std::pair<int, float>>> adj(n);
2528 for (const stl_triangle_vertex_indices &tri : patch.indices)
2529 for (int i = 0; i < 3; ++i) {
2530 const int a = tri[i], b = tri[(i + 1) % 3];
2531 if (a < 0 || b < 0 || size_t(a) >= n || size_t(b) >= n)
2532 continue;
2533 const float w = (patch.vertices[size_t(a)] - patch.vertices[size_t(b)]).norm();
2534 adj[size_t(a)].push_back({ b, w });
2535 adj[size_t(b)].push_back({ a, w });
2536 }
2537
2538 std::vector<float> dist(n, std::numeric_limits<float>::infinity());
2539 using QN = std::pair<float, int>;
2540 std::priority_queue<QN, std::vector<QN>, std::greater<QN>> pq;
2541 for (size_t v = 0; v < n; ++v)
2542 if (v < is_boundary.size() && is_boundary[v]) {
2543 dist[v] = 0.f;
2544 pq.push({ 0.f, int(v) });
2545 }
2546 while (!pq.empty()) {
2547 const auto [d, u] = pq.top();
2548 pq.pop();
2549 if (d > dist[size_t(u)])
2550 continue;
2551 for (const auto &[w, ew] : adj[size_t(u)]) {
2552 const float nd = d + ew;
2553 if (nd < dist[size_t(w)]) {
2554 dist[size_t(w)] = nd;
2555 pq.push({ nd, w });
2556 }
2557 }
2558 }
2559 return dist;
2560}
T dist(const boost::polygon::point_data< T > &p1, const boost::polygon::point_data< T > &p2)
Definition Geometry.cpp:298

References Slic3r::Geometry::dist(), and patch_boundary_distance().

Referenced by patch_boundary_distance().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ prepare_sampleable_layers()

std::shared_ptr< std::vector< PreparedLayer > > Slic3r::anonymous_namespace{TextureDisplacement.cpp}::prepare_sampleable_layers ( const indexed_triangle_set & base_mesh,
const std::vector< TextureDisplacementLayer > & layers,
const TextureDisplacementFacetsData & facets_data,
bool need_color,
const std::vector< Vec2f > * model_uv )
3762{
3763 auto prepared = std::make_shared<std::vector<PreparedLayer>>();
3764
3765 if (base_mesh.indices.empty())
3766 return prepared;
3767
3768 std::vector<const TextureDisplacementLayer *> ordered;
3769 for (const TextureDisplacementLayer &l : layers)
3770 if (l.slot >= 0 && l.slot < int(TEXTURE_DISPLACEMENT_MAX_LAYERS))
3771 ordered.push_back(&l);
3772 std::sort(ordered.begin(), ordered.end(),
3773 [](const TextureDisplacementLayer *a, const TextureDisplacementLayer *b) { return a->slot < b->slot; });
3774
3775 const std::vector<Vec3f> vertex_normals = texture_displacement_vertex_normals(base_mesh);
3776 const TriangleMesh selector_mesh(base_mesh);
3777 const float mesh_area = area_3d(base_mesh);
3778
3779 for (const TextureDisplacementLayer *layer : ordered) {
3780 // Unwrap layers used to be skipped here ("no per-point UV"). That made the default pipeline
3781 // bake an unwrap layer as nothing at all - and since the job then clears the baked layers'
3782 // paint, the painted region simply vanished. They get an LscmLookup below instead.
3783 if (need_color && !layer->color_enabled)
3784 continue;
3785 const TriangleSelector::TriangleSplittingData &data = facets_data[size_t(layer->slot)];
3786 if (data.triangles_to_split.empty())
3787 continue;
3788 const DecodedHeightTexture tex = decode_height_texture(*layer);
3789 if (tex.empty() || (need_color && !tex.has_color()))
3790 continue;
3791
3792 const bool model_uv_layer = layer->projection_method == TextureProjectionMethod::ModelUV &&
3793 model_uv != nullptr && model_uv->size() == base_mesh.indices.size() * 3;
3794
3795 TriangleSelector selector(selector_mesh);
3796 selector.deserialize(data, false);
3797 std::vector<int> patch_source; // only the model's own channel needs it; see the classic path
3798 const indexed_triangle_set patch =
3799 selector.get_facets_strict(EnforcerBlockerType::ENFORCER, model_uv_layer ? &patch_source : nullptr);
3800 if (patch.indices.empty())
3801 continue;
3802
3803 // Patch centroid + cylinder axis, shared with build_texture_displacement() so a
3804 // Cylindrical/Spherical layer's detach criterion matches the geometry the bake will produce.
3805 Vec3f centroid, axis, average_normal;
3806 texture_displacement_patch_frame(patch, vertex_normals, centroid, axis, average_normal);
3807
3808 std::shared_ptr<const LscmLookup> lscm;
3809 if (model_uv_layer || layer->projection_method == TextureProjectionMethod::LSCM) {
3810 // Solved on the very patch the classic path and the GUI solve it on (same geometry, seam
3811 // angle and edges), so it hits the unwrap cache and lands exactly where the UV editor
3812 // shows it, hand-placed islands and UV edits included. A ModelUV layer needs no solve at
3813 // all - its corners are read straight out of the volume's channel - but it is looked up
3814 // through the same per-triangle machinery, for the same reason: there is no formula from a
3815 // point to its uv.
3816 auto l = std::make_shared<LscmLookup>();
3817 l->corner = model_uv_layer ? compute_model_corner_uvs(base_mesh, *model_uv, patch, patch_source) :
3818 compute_lscm_corner_uvs(patch, *layer);
3819 if (l->corner.size() == patch.indices.size() * 3) {
3820 l->patch = patch;
3821 l->tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(l->patch.vertices, l->patch.indices);
3822 lscm = std::move(l);
3823 }
3824 }
3825
3826 std::shared_ptr<const PatchRegion> region;
3827 if (!lscm && area_3d(patch) < 0.9999f * mesh_area) {
3828 auto r = std::make_shared<PatchRegion>();
3829 r->patch = patch;
3830 r->tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(r->patch.vertices, r->patch.indices);
3831 region = std::move(r);
3832 }
3833
3834 prepared->push_back({ tex, *layer, centroid, axis, std::move(lscm), std::move(region) });
3835 }
3836 return prepared;
3837}
float area_3d(const indexed_triangle_set &mesh)
Definition TextureDisplacement.cpp:1179
std::vector< Vec3f > texture_displacement_vertex_normals(const indexed_triangle_set &its)
Definition TextureDisplacement.cpp:2499
std::vector< Vec2f > compute_model_corner_uvs(const indexed_triangle_set &base_mesh, const std::vector< Vec2f > &model_uv, const indexed_triangle_set &patch, const std::vector< int > &patch_source)
Definition TextureDisplacement.cpp:2076
void texture_displacement_patch_frame(const indexed_triangle_set &patch, const std::vector< Vec3f > &vertex_normals, Vec3f &center, Vec3f &axis, Vec3f &average_normal)
Definition TextureDisplacement.cpp:3549
static constexpr size_t TEXTURE_DISPLACEMENT_MAX_LAYERS
Definition TextureDisplacement.hpp:44
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer, bool keep_decoded)
Definition TextureDisplacement.cpp:286
std::vector< Vec2f > compute_lscm_corner_uvs(const indexed_triangle_set &patch, const TextureDisplacementLayer &layer)
Definition TextureDisplacement.cpp:2024
@ ModelUV
Definition TextureDisplacement.hpp:99
@ LSCM
Definition TextureDisplacement.hpp:82
Definition TextureDisplacement.hpp:531
bool empty() const
Definition TextureDisplacement.hpp:541
bool has_color() const
Definition TextureDisplacement.hpp:542
Definition TextureDisplacement.hpp:210
std::vector< TriangleBitStreamMapping > triangles_to_split
Definition TriangleSelector.hpp:289

References area_3d(), Slic3r::AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(), Slic3r::compute_lscm_corner_uvs(), Slic3r::compute_model_corner_uvs(), Slic3r::decode_height_texture(), Slic3r::TriangleSelector::deserialize(), Slic3r::DecodedHeightTexture::empty(), Slic3r::ENFORCER, Slic3r::TriangleSelector::get_facets_strict(), Slic3r::DecodedHeightTexture::has_color(), Slic3r::LSCM, Slic3r::ModelUV, prepare_sampleable_layers(), Slic3r::TEXTURE_DISPLACEMENT_MAX_LAYERS, Slic3r::texture_displacement_patch_frame(), Slic3r::texture_displacement_vertex_normals(), and Slic3r::TriangleSelector::TriangleSplittingData::triangles_to_split.

Referenced by prepare_sampleable_layers().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ project_cylindrical()

Vec2f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::project_cylindrical ( const Vec3f & position,
const Vec3f & patch_center,
const Vec3f & patch_axis )
820{
821 Vec3f up = patch_axis;
822 up = (up.norm() > 1e-8f) ? Vec3f(up.normalized()) : Vec3f::UnitZ();
823 const Vec3f arbitrary = (std::abs(up.dot(Vec3f::UnitZ())) < 0.9f) ? Vec3f::UnitZ() : Vec3f::UnitX();
824 const Vec3f right = Vec3f(up.cross(arbitrary).normalized());
825 // up x right, not right x up: that makes (right, fwd, up) right-handed, so the angle below grows
826 // anticlockwise about the axis - which, seen from outside the cylinder, is to the viewer's right.
827 // The other way round the texture wraps on mirrored, as if seen through the model from behind.
828 const Vec3f fwd = Vec3f(up.cross(right).normalized());
829
830 const Vec3f rel = position - patch_center;
831 const float along_axis = rel.dot(up);
832 const float x = rel.dot(right);
833 const float y = rel.dot(fwd);
834 const float radius = std::sqrt(x * x + y * y);
835 const float angle = std::atan2(y, x);
836
837 // Negated, like every other projection's v: v = 0 is the texture's top row, so v has to grow
838 // *down* the axis for the top of the image to sit at the top of the model.
839 return Vec2f(angle * radius, -along_axis);
840}
double angle(const Eigen::MatrixBase< Derived > &v1, const Eigen::MatrixBase< Derived2 > &v2)
Definition Point.hpp:138

References Slic3r::angle(), and project_cylindrical().

Referenced by project_cylindrical().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ project_spherical()

Vec2f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::project_spherical ( const Vec3f & position,
const Vec3f & patch_center )
846{
847 const Vec3f rel = position - patch_center;
848 const float radius = rel.norm();
849 if (radius < 1e-8f)
850 return Vec2f::Zero();
851
852 const Vec3f dir = rel / radius;
853 const float longitude = std::atan2(dir.y(), dir.x());
854 const float latitude = std::asin(std::clamp(dir.z(), -1.f, 1.f));
855 // Longitude already grows to the right as seen from outside; latitude grows toward the north
856 // pole, so it is negated to put the image's top row (v = 0) at the top of the model.
857 return Vec2f(longitude, -latitude) * radius;
858}

References project_spherical().

Referenced by project_spherical().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ project_to_tangent_plane()

std::vector< Vec2f > Slic3r::anonymous_namespace{TextureDisplacement.cpp}::project_to_tangent_plane ( const indexed_triangle_set & chart,
const Vec3f & normal )
1151{
1152 // Oriented like the box projection (see box_projection_axes()), so a chart reads the right way up
1153 // and unmirrored instead of being flattened to whichever basis a cross product happened to give.
1154 // For a chart whose normal *is* a world axis - a cube face, which is the common case once the
1155 // seam angle has cut the patch along its sharp edges - this is exactly the box basis, so such a
1156 // face unwraps to the same orientation Triplanar would have given it.
1157 Vec3f u, v;
1159 // The box axes lie in the tangent plane already when the normal is a world axis; for any other
1160 // normal, u is squared up against it and v follows. u x v == -normal keeps the chart the way
1161 // round a viewer outside the model sees it (a plain normal x u would mirror it).
1162 u = u - normal * normal.dot(u);
1163 if (u.norm() <= 1e-6f) {
1164 // Out of reach for a unit normal - the box basis puts u perpendicular to the normal's own
1165 // dominant axis, so the two can never be within 45 degrees of each other - but a caller
1166 // handing over an unnormalized normal should still get a usable basis rather than a NaN.
1167 const Vec3f seed = (std::abs(normal.z()) < 0.9f) ? Vec3f::UnitZ() : Vec3f::UnitX();
1168 u = seed.cross(normal);
1169 }
1170 u = Vec3f(u.normalized());
1171 v = Vec3f(u.cross(normal).normalized());
1172
1173 std::vector<Vec2f> uvs(chart.vertices.size());
1174 for (size_t i = 0; i < chart.vertices.size(); ++i)
1175 uvs[i] = Vec2f(chart.vertices[i].dot(u), chart.vertices[i].dot(v));
1176 return uvs;
1177}
void box_projection_axes(const Vec3f &normal, Vec3f &u_axis, Vec3f &v_axis)
Definition TextureDisplacement.cpp:780

References Slic3r::box_projection_axes(), Slic3r::normal(), and project_to_tangent_plane().

Referenced by project_to_tangent_plane().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ read()

template<class T>
void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::read ( const nlohmann::json & j,
const char * k,
T & out )
5750{
5751 const auto it = j.find(k);
5752 if (it == j.end())
5753 return;
5754 try {
5755 out = it->get<T>();
5756 } catch (...) {
5757 }
5758}

References read().

Referenced by read().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ read_enum()

void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::read_enum ( const nlohmann::json & j,
const char * k,
int & out )
5760{
5761 const auto it = j.find(k);
5762 if (it != j.end() && it->is_number_integer())
5763 out = it->get<int>();
5764}

References read_enum().

Referenced by read_enum().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ scan_steps()

StepScan Slic3r::anonymous_namespace{TextureDisplacement.cpp}::scan_steps ( const indexed_triangle_set & mesh,
const std::vector< uint8_t > & region,
const HeightFieldSampler & sampler,
float step_width_mm,
float seam_gap_mm,
float min_feature_mm,
bool nudge )
4727{
4728 StepScan scan;
4729 const size_t nv = mesh.vertices.size(), nt = mesh.indices.size();
4730 if (!sampler || region.size() != nt || step_width_mm <= 0.f)
4731 return scan;
4732
4733 // 1. Heights at the vertices of the cuttable triangles, along the same normals the bake will use.
4734 // Vertex positions are worked on in `pos`: a vertex that happens to sit within a texel of a step
4735 // is nudged off it below, and everything from the edge march to the output uses the nudged place.
4736 std::vector<Vec3f> &pos = scan.pos;
4737 std::vector<Vec3f> &vnormal = scan.vnormal;
4738 pos = mesh.vertices;
4740 std::vector<uint8_t> wanted(nv, 0);
4741 for (size_t t = 0; t < nt; ++t)
4742 if (region[t] != 0)
4743 for (int k = 0; k < 3; ++k)
4744 wanted[size_t(mesh.indices[t][k])] = 1;
4745 std::vector<float> &vh = scan.vh;
4746 vh.assign(nv, 0.f);
4747 tbb::parallel_for(tbb::blocked_range<size_t>(0, nv), [&](const tbb::blocked_range<size_t> &r) {
4748 for (size_t v = r.begin(); v < r.end(); ++v)
4749 if (wanted[v])
4750 vh[v] = sampler(pos[v], vnormal[v]);
4751 });
4752 float hmin = std::numeric_limits<float>::max(), hmax = -hmin;
4753 for (size_t v = 0; v < nv; ++v)
4754 if (wanted[v]) { hmin = std::min(hmin, vh[v]); hmax = std::max(hmax, vh[v]); }
4755 const float range = scan.range = hmax - hmin;
4756 if (!(range > 1e-6f))
4757 return scan;
4758 const float iso = scan.iso = 0.5f * (hmin + hmax);
4759 // Only a texture that is mostly steps is cut: one whose heights sit at two levels with little in
4760 // between (a grid, a knurl, a logo). On a smooth or noisy relief the mid-level contour runs through
4761 // every triangle without being a step anywhere, and cutting along it would only multiply the
4762 // triangles; the chord-based refinement is the right tool there. Judged from the vertex heights,
4763 // which sample the whole painted surface.
4764 {
4765 size_t near_level = 0, total = 0;
4766 for (size_t v = 0; v < nv; ++v)
4767 if (wanted[v]) { ++total; near_level += std::abs(vh[v] - iso) > 0.35f * range; }
4768 if (total == 0 || double(near_level) < 0.6 * double(total))
4769 return scan;
4770 }
4771
4772 // 1b. A vertex inside the blend of a sharp step - the texture's bilinear ramp is a texel wide - would
4773 // be displaced to a height between the two sides, and every triangle at it would ramp. Such a vertex
4774 // is nudged along the surface away from the step, down its own side's slope, until it samples a
4775 // pure value; a step's width or so. Only where the surface is flat around it: a vertex on a crease
4776 // of the model (a box edge) stays where it is. Skipped by a probe, which only wants the verdict.
4777 if (nudge) {
4778 std::vector<uint8_t> creased(nv, 0);
4779 std::vector<Vec3f> first_normal(nv, Vec3f::Zero());
4780 std::vector<float> shortest(nv, std::numeric_limits<float>::max()); // shortest edge at the vertex
4781 for (size_t t = 0; t < nt; ++t) {
4782 const auto &tri = mesh.indices[t];
4783 const Vec3f &a = pos[size_t(tri[0])];
4784 Vec3f fn = (pos[size_t(tri[1])] - a).cross(pos[size_t(tri[2])] - a);
4785 const float fl = fn.norm();
4786 if (fl < 1e-12f) continue;
4787 fn /= fl;
4788 for (int k = 0; k < 3; ++k) {
4789 Vec3f &f0 = first_normal[size_t(tri[k])];
4790 if (f0.isZero()) f0 = fn;
4791 else if (f0.dot(fn) < 0.985f) creased[size_t(tri[k])] = 1; // ~10 degrees
4792 const float el = (pos[size_t(tri[(k + 1) % 3])] - pos[size_t(tri[k])]).norm();
4793 shortest[size_t(tri[k])] = std::min(shortest[size_t(tri[k])], el);
4794 shortest[size_t(tri[(k + 1) % 3])] = std::min(shortest[size_t(tri[(k + 1) % 3])], el);
4795 }
4796 }
4797 tbb::parallel_for(tbb::blocked_range<size_t>(0, nv), [&](const tbb::blocked_range<size_t> &r) {
4798 for (size_t v = r.begin(); v < r.end(); ++v) {
4799 if (!wanted[v] || creased[v] || std::abs(vh[v] - iso) > 0.47f * range)
4800 continue;
4801 const Vec3f &n = vnormal[v];
4802 const Vec3f ax = (std::abs(n.x()) < 0.9f) ? Vec3f::UnitX() : Vec3f::UnitY();
4803 const Vec3f t1 = n.cross(ax).normalized(), t2 = n.cross(t1).normalized();
4804 const float d = step_width_mm;
4805 const float gx = sampler(pos[v] + t1 * d, n) - sampler(pos[v] - t1 * d, n);
4806 const float gy = sampler(pos[v] + t2 * d, n) - sampler(pos[v] - t2 * d, n);
4807 Vec3f g = t1 * gx + t2 * gy;
4808 const float gl = g.norm();
4809 if (gl < 0.25f * range)
4810 continue; // not at a step: a slope, which the chord test handles
4811 g /= gl;
4812 // Down the slope for a low vertex, up it for a high one, in steps of half a texel, and
4813 // never by more than a fraction of the shortest edge at the vertex: on a finely refined
4814 // mesh a texel-sized move would fold the triangles around it.
4815 const Vec3f dir = (vh[v] > iso) ? g : Vec3f(-g);
4816 Vec3f best = pos[v];
4817 float bh = vh[v];
4818 const float limit = std::min(2.f * step_width_mm, 0.3f * shortest[v]);
4819 for (int i = 1; i <= 8; ++i) {
4820 const float dist = 0.25f * step_width_mm * float(i);
4821 if (dist > limit)
4822 break;
4823 const Vec3f q = pos[v] + dir * dist;
4824 const float h = sampler(q, n);
4825 if ((h > iso) != (vh[v] > iso))
4826 break; // crossed the step: the nudge would change the vertex's side
4827 best = q; bh = h;
4828 if (std::abs(h - iso) > 0.48f * range)
4829 break;
4830 }
4831 if (std::abs(bh - iso) > std::abs(vh[v] - iso)) { pos[v] = best; vh[v] = bh; }
4832 }
4833 });
4834 }
4835
4836 const auto sample_between = [&scan](int a, int b, float t) { return scan.sample_between(a, b, t); };
4837 const auto side_of = [&scan](float h) { return scan.side_of(h); };
4838
4839 // 2. Crossings on the edges, found on the field itself - on a 1 mm edge over a texture with 30 um
4840 // texels, interpolating the endpoint heights could put a step anywhere along the edge. Both
4841 // triangles on an edge see the same crossings, which is what keeps the cut conformal.
4842 auto &edges = scan.edges;
4843 edges.reserve(nt);
4844
4845 const auto find_crossings = [&](int a, int b, Edge &edge) {
4846 if (a > b) std::swap(a, b);
4847 const float len = (pos[size_t(b)] - pos[size_t(a)]).norm();
4848 const int steps = std::clamp(int(std::ceil(len / (0.5f * step_width_mm))), 4, 128);
4849 float t0 = 0.f, h0 = vh[size_t(a)] - iso;
4850 for (int i = 1; i <= steps; ++i) {
4851 const float ti = float(i) / float(steps);
4852 const auto [pp, nn] = sample_between(a, b, ti);
4853 const float hi_ = sampler(pp, nn) - iso;
4854 if (h0 * hi_ < 0.f || (hi_ == 0.f && h0 != 0.f)) {
4855 float lo_t = t0, hi_t = ti, lo_h = h0;
4856 for (int k = 0; k < 10; ++k) {
4857 const float tm = 0.5f * (lo_t + hi_t);
4858 const auto [pm, nm] = sample_between(a, b, tm);
4859 if (lo_h * (sampler(pm, nm) - iso) <= 0.f) hi_t = tm; else { lo_t = tm; }
4860 }
4861 Crossing c;
4862 c.a = a; c.b = b;
4863 // Never exactly on a vertex: that would make a zero-area triangle of the split.
4864 c.t = std::clamp(0.5f * (lo_t + hi_t), 1e-3f, 1.f - 1e-3f);
4865 std::tie(c.p, c.n) = sample_between(a, b, c.t);
4866 // Sharp means the field changes by at least half its range across `step_width_mm` in
4867 // the steepest tangent direction - a step, not a slope that merely passes mid-level.
4868 const Vec3f ax = (std::abs(c.n.x()) < 0.9f) ? Vec3f::UnitX() : Vec3f::UnitY();
4869 const Vec3f t1v = c.n.cross(ax).normalized(), t2v = c.n.cross(t1v).normalized();
4870 const float d = 0.5f * step_width_mm;
4871 const float gx = sampler(c.p + t1v * d, c.n) - sampler(c.p - t1v * d, c.n);
4872 const float gy = sampler(c.p + t2v * d, c.n) - sampler(c.p - t2v * d, c.n);
4873 const float gl = std::sqrt(gx * gx + gy * gy);
4874 c.sharp = gl >= 0.5f * range;
4875 // How wide the feature is on either side of the step, along the gradient: the
4876 // distance until the field returns to mid-level, in steps of half the step width, up
4877 // to `thick_max`. A texture whose features are only a few texels wide has no pure
4878 // interior for a seam copy to land in and is left to refinement (see the gate below).
4879 {
4880 const float thick_max = 4.f * step_width_mm;
4881 c.thick = thick_max;
4882 if (gl > 1e-12f) {
4883 const Vec3f g = (t1v * gx + t2v * gy) / gl;
4884 for (int sgn = -1; sgn <= 1; sgn += 2) {
4885 const bool high = sgn > 0; // uphill along the gradient
4886 for (int i = 1; i <= 8; ++i) {
4887 const float dist = 0.5f * step_width_mm * float(i);
4888 if (dist >= c.thick)
4889 break;
4890 if (side_of(sampler(c.p + g * (float(sgn) * dist), c.n)) != high) {
4891 c.thick = std::min(c.thick, dist);
4892 break;
4893 }
4894 }
4895 }
4896 }
4897 }
4898 edge.xs.push_back(c);
4899 }
4900 t0 = ti; h0 = hi_;
4901 }
4902 // A pair of crossings closer than the smallest feature the mesh is meant to carry is a feature
4903 // too thin to print, and one closer than the seam gap has no room for two seams: dropping both
4904 // leaves the surface flat there, at the side around it.
4905 const float thinnest = std::max(min_feature_mm, seam_gap_mm);
4906 for (size_t i = 0; i + 1 < edge.xs.size();) {
4907 if ((edge.xs[i + 1].t - edge.xs[i].t) * len < thinnest)
4908 edge.xs.erase(edge.xs.begin() + long(i), edge.xs.begin() + long(i) + 2);
4909 else
4910 ++i;
4911 }
4912 // Parity: the endpoints' sides say whether the count must be odd or even; the march starts and
4913 // ends on the vertex heights themselves, so a mismatch is a numerical accident. Drop the lot
4914 // then - a triangle split on a wrong count is worse than a ramp there.
4915 const bool odd_expected = side_of(vh[size_t(a)]) != side_of(vh[size_t(b)]);
4916 if ((edge.xs.size() % 2 == 1) != odd_expected)
4917 edge.xs.clear();
4918 };
4919
4920 // Edges to examine, in a fixed order so the crossings can be found in parallel.
4921 std::vector<uint64_t> edge_keys;
4922 for (size_t t = 0; t < nt; ++t) {
4923 if (region[t] == 0)
4924 continue;
4925 const auto &tri = mesh.indices[t];
4926 for (int e = 0; e < 3; ++e)
4927 edge_keys.push_back(step_edge_key(tri[e], tri[(e + 1) % 3]));
4928 }
4929 std::sort(edge_keys.begin(), edge_keys.end());
4930 edge_keys.erase(std::unique(edge_keys.begin(), edge_keys.end()), edge_keys.end());
4931 std::vector<Edge> found(edge_keys.size());
4932 tbb::parallel_for(tbb::blocked_range<size_t>(0, edge_keys.size()), [&](const tbb::blocked_range<size_t> &r) {
4933 for (size_t i = r.begin(); i < r.end(); ++i)
4934 find_crossings(int(edge_keys[i] >> 32), int(uint32_t(edge_keys[i])), found[i]);
4935 });
4936 size_t crossings = 0, sharp = 0;
4937 double thick_sum = 0.;
4938 for (size_t i = 0; i < edge_keys.size(); ++i) {
4939 for (const Crossing &c : found[i].xs) { ++crossings; sharp += c.sharp; thick_sum += c.thick; }
4940 edges.emplace(edge_keys[i], std::move(found[i]));
4941 }
4942 // The mid-level contour has to be a step nearly everywhere it is crossed, or this is not a step
4943 // texture: a noisy relief that happens to sit at two levels is crossed all over, mostly gently.
4944 if (crossings == 0 || double(sharp) < 0.8 * double(crossings))
4945 return scan;
4946 // And the features have to be wider than the step itself. A texture whose blobs are only a few
4947 // texels across (noise, a fine grain) is bimodal and sharp at every crossing, yet has no pure
4948 // interior for the seam copies to land in, and its contour runs through every triangle: cutting it
4949 // doubles the triangle count for walls the same size as the interpolation blur. Judged from the
4950 // mean feature thickness at the crossings, measured in step widths (2.25 = four to five texels).
4951 if (thick_sum < 2.25 * double(step_width_mm) * double(crossings))
4952 return scan;
4953
4954 scan.ok = true;
4955 return scan;
4956
4957}
int a
Definition TextureDisplacement.cpp:4679
uint64_t step_edge_key(int a, int b)
Definition TextureDisplacement.cpp:4695
Definition TextureDisplacement.cpp:4691
Vec3d pos(const Pt &p)
Definition ReprojectPointsOnMesh.hpp:16
int sgn(T val)
Definition Fill3DHoneycomb.cpp:24
float iso
Definition TextureDisplacement.cpp:4710
bool side_of(float h) const
Definition TextureDisplacement.cpp:4721
std::vector< Vec3f > pos
Definition TextureDisplacement.cpp:4708
std::pair< Vec3f, Vec3f > sample_between(int a, int b, float t) const
Definition TextureDisplacement.cpp:4713
float range
Definition TextureDisplacement.cpp:4710
std::vector< float > vh
Definition TextureDisplacement.cpp:4709
std::unordered_map< uint64_t, Edge > edges
Definition TextureDisplacement.cpp:4711
std::vector< Vec3f > vnormal
Definition TextureDisplacement.cpp:4708
bool ok
Definition TextureDisplacement.cpp:4707

References Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing::a, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing::b, Slic3r::cross(), Slic3r::Geometry::dist(), Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::edges, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::iso, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing::n, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::ok, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing::p, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::pos, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::range, Slic3r::range(), Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::sample_between(), scan_steps(), Slic3r::sgn(), Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing::sharp, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::side_of(), step_edge_key(), Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing::t, Slic3r::texture_displacement_vertex_normals(), Slic3r::anonymous_namespace{TextureDisplacement.cpp}::Crossing::thick, Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::vh, and Slic3r::anonymous_namespace{TextureDisplacement.cpp}::StepScan::vnormal.

Referenced by scan_steps().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ segment_into_charts()

std::vector< int > Slic3r::anonymous_namespace{TextureDisplacement.cpp}::segment_into_charts ( const indexed_triangle_set & mesh,
const std::vector< Vec3f > & normals,
float seam_angle_deg,
const std::unordered_set< uint64_t > & seam_keys,
int & chart_count )
1030{
1031 constexpr int MAX_SPLIT_DEPTH = 8;
1032 const int n_faces = int(mesh.indices.size());
1033 const float cos_threshold = std::cos(std::clamp(seam_angle_deg, 0.f, 180.f) * float(M_PI) / 180.f);
1034
1035 // Each shared edge, with the (up to two) faces on it.
1036 std::unordered_map<uint64_t, std::pair<int, int>> edge_faces;
1037 edge_faces.reserve(size_t(n_faces) * 3);
1038 for (int f = 0; f < n_faces; ++f) {
1039 const stl_triangle_vertex_indices &tri = mesh.indices[size_t(f)];
1040 for (int i = 0; i < 3; ++i) {
1041 const uint64_t key = undirected_edge_key(tri[i], tri[(i + 1) % 3]);
1042 const auto [it, inserted] = edge_faces.emplace(key, std::make_pair(f, -1));
1043 if (!inserted)
1044 it->second.second = f;
1045 }
1046 }
1047
1048 // Faces joined across each edge that does not cut. A face has three edges, so at most three such neighbours.
1049 std::vector<std::array<int, 3>> adjacent(static_cast<size_t>(n_faces), { -1, -1, -1 });
1050 const auto link = [&adjacent](int f, int nb) {
1051 for (int &slot : adjacent[size_t(f)])
1052 if (slot < 0) {
1053 slot = nb;
1054 return;
1055 }
1056 };
1057 for (const auto &[key, fp] : edge_faces) {
1058 if (fp.second < 0 || fp.first == fp.second)
1059 continue; // a boundary edge of the patch, nothing on the far side to join
1060 // A manually/auto marked seam always cuts, whatever the dihedral angle - that is exactly
1061 // what lets "mark seam" / "cut island" split a chart that is otherwise flat enough to merge.
1062 if (!seam_keys.empty() && seam_keys.count(key))
1063 continue;
1064 if (normals[size_t(fp.first)].dot(normals[size_t(fp.second)]) >= cos_threshold) {
1065 link(fp.first, fp.second);
1066 link(fp.second, fp.first);
1067 }
1068 }
1069
1070 // Connected pieces of `faces` over `adjacent`, staying within each face's current `group`.
1071 std::vector<int> group(static_cast<size_t>(n_faces), 0), visited(static_cast<size_t>(n_faces), -1);
1072 int visit_pass = 0;
1073 const auto connected_pieces = [&](const std::vector<int> &faces, std::vector<std::vector<int>> &out) {
1074 const int pass = visit_pass++;
1075 std::vector<int> stack;
1076 for (const int seed : faces) {
1077 if (visited[size_t(seed)] == pass)
1078 continue;
1079 visited[size_t(seed)] = pass;
1080 std::vector<int> piece{ seed };
1081 stack.assign(1, seed);
1082 while (!stack.empty()) {
1083 const int f = stack.back();
1084 stack.pop_back();
1085 for (const int nb : adjacent[size_t(f)])
1086 if (nb >= 0 && visited[size_t(nb)] != pass && group[size_t(nb)] == group[size_t(f)]) {
1087 visited[size_t(nb)] = pass;
1088 piece.push_back(nb);
1089 stack.push_back(nb);
1090 }
1091 }
1092 out.push_back(std::move(piece));
1093 }
1094 };
1095
1096 std::vector<int> all_faces(static_cast<size_t>(n_faces));
1097 std::iota(all_faces.begin(), all_faces.end(), 0);
1098 std::vector<std::vector<int>> pieces;
1099 connected_pieces(all_faces, pieces);
1100 std::vector<std::pair<std::vector<int>, int>> pending; // a piece, and how many cuts produced it
1101 for (std::vector<int> &piece : pieces)
1102 pending.emplace_back(std::move(piece), 0);
1103
1104 std::vector<std::vector<int>> charts;
1105 int next_group = 1;
1106 while (!pending.empty()) {
1107 std::vector<int> faces = std::move(pending.back().first);
1108 const int depth = pending.back().second;
1109 pending.pop_back();
1110 if (faces.size() < 2 || depth >= MAX_SPLIT_DEPTH || chart_is_flattenable(mesh, normals, faces)) {
1111 charts.push_back(std::move(faces));
1112 continue;
1113 }
1114 const std::vector<char> side = split_chart_sides(mesh, normals, faces);
1115 std::vector<int> halves[2];
1116 for (size_t i = 0; i < faces.size(); ++i)
1117 halves[side[i] ? 1 : 0].push_back(faces[i]);
1118 if (halves[0].empty() || halves[1].empty()) {
1119 charts.push_back(std::move(faces));
1120 continue;
1121 }
1122 for (std::vector<int> &half : halves) {
1123 for (const int f : half)
1124 group[size_t(f)] = next_group;
1125 ++next_group;
1126 pieces.clear();
1127 connected_pieces(half, pieces);
1128 for (std::vector<int> &piece : pieces)
1129 pending.emplace_back(std::move(piece), depth + 1);
1130 }
1131 }
1132
1133 // Chart ids in first-encountered-triangle order, which TextureIsland indexing documents.
1134 std::vector<std::pair<int, size_t>> first_face(charts.size());
1135 for (size_t c = 0; c < charts.size(); ++c)
1136 first_face[c] = { *std::min_element(charts[c].begin(), charts[c].end()), c };
1137 std::sort(first_face.begin(), first_face.end());
1138
1139 std::vector<int> chart_of(static_cast<size_t>(n_faces), -1);
1140 for (size_t id = 0; id < first_face.size(); ++id)
1141 for (const int f : charts[first_face[id].second])
1142 chart_of[size_t(f)] = int(id);
1143 chart_count = int(charts.size());
1144 return chart_of;
1145}
#define M_PI
Definition ExtrusionSimulator.cpp:31
T * begin(Slic3r::Mat< N, M, T > &mat)
Definition Point.hpp:838
T * end(Slic3r::Mat< N, M, T > &mat)
Definition Point.hpp:841
std::vector< char > split_chart_sides(const indexed_triangle_set &mesh, const std::vector< Vec3f > &normals, const std::vector< int > &faces)
Definition TextureDisplacement.cpp:985
bool chart_is_flattenable(const indexed_triangle_set &mesh, const std::vector< Vec3f > &normals, const std::vector< int > &faces)
Definition TextureDisplacement.cpp:929
bool empty(const BoundingBoxBase< PointType, PointsType > &bb)
Definition BoundingBox.hpp:273

References chart_is_flattenable(), Slic3r::empty(), Slic3r::f(), M_PI, segment_into_charts(), split_chart_sides(), and undirected_edge_key().

Referenced by segment_into_charts().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ simplify_polyline()

void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::simplify_polyline ( const std::vector< Vec2f > & in,
float tol,
std::vector< Vec2f > & out )
4640{
4641 out.clear();
4642 if (in.size() < 3) { out = in; return; }
4643 std::vector<uint8_t> keep(in.size(), 0);
4644 keep.front() = keep.back() = 1;
4645 std::vector<std::pair<size_t, size_t>> stack = { { 0, in.size() - 1 } };
4646 while (!stack.empty()) {
4647 const auto [i0, i1] = stack.back();
4648 stack.pop_back();
4649 if (i1 <= i0 + 1)
4650 continue;
4651 const Vec2f d = in[i1] - in[i0];
4652 const float len = d.norm();
4653 float best = -1.f;
4654 size_t bi = i0;
4655 for (size_t i = i0 + 1; i < i1; ++i) {
4656 const Vec2f r = in[i] - in[i0];
4657 const float dist = (len > 1e-12f) ? std::abs(d.x() * r.y() - d.y() * r.x()) / len : r.norm();
4658 if (dist > best) { best = dist; bi = i; }
4659 }
4660 if (best > tol) {
4661 keep[bi] = 1;
4662 stack.push_back({ i0, bi });
4663 stack.push_back({ bi, i1 });
4664 }
4665 }
4666 for (size_t i = 0; i < in.size(); ++i)
4667 if (keep[i]) out.push_back(in[i]);
4668}

References Slic3r::Geometry::dist(), and simplify_polyline().

Referenced by simplify_polyline().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ smooth_height_pixels()

void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::smooth_height_pixels ( std::vector< uint8_t > & pixels,
int width,
int height,
float radius )
258{
259 if (radius <= 0.f || width <= 0 || height <= 0 || pixels.size() != size_t(width) * size_t(height))
260 return;
261
262 const int whole = std::max(1, int(std::ceil(radius)));
263 const float mix = std::clamp(radius / float(whole), 0.f, 1.f);
264 const std::vector<uint8_t> raw = (mix < 0.999f) ? pixels : std::vector<uint8_t>{};
265 smooth_height_pixels_box(pixels, width, height, whole);
266 if (!raw.empty())
267 for (size_t i = 0; i < pixels.size(); ++i)
268 pixels[i] = uint8_t(std::lround(float(raw[i]) + (float(pixels[i]) - float(raw[i])) * mix));
269}
void smooth_height_pixels_box(std::vector< uint8_t > &pixels, int width, int height, int radius)
Definition TextureDisplacement.cpp:200
STL namespace.

References smooth_height_pixels(), and smooth_height_pixels_box().

Referenced by adjust_channel_spatial(), and smooth_height_pixels().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ smooth_height_pixels_box()

void Slic3r::anonymous_namespace{TextureDisplacement.cpp}::smooth_height_pixels_box ( std::vector< uint8_t > & pixels,
int width,
int height,
int radius )
201{
202 if (radius <= 0 || width <= 0 || height <= 0 || pixels.size() != size_t(width) * size_t(height))
203 return;
204
205 const int window = 2 * radius + 1;
206 const float inv = 1.f / float(window);
207 const auto wrap = [](int i, int n) { return (i % n + n) % n; };
208 std::vector<uint8_t> tmp(pixels.size());
209
210 for (int pass = 0; pass < 2; ++pass) { // two passes -> smoother than a single box
211 // Horizontal: a running sum per row.
212 tbb::parallel_for(tbb::blocked_range<int>(0, height), [&](const tbb::blocked_range<int> &r) {
213 for (int y = r.begin(); y < r.end(); ++y) {
214 const uint8_t *src = pixels.data() + size_t(y) * size_t(width);
215 uint8_t *dst = tmp.data() + size_t(y) * size_t(width);
216 uint32_t sum = 0;
217 for (int k = -radius; k <= radius; ++k)
218 sum += src[wrap(k, width)];
219 for (int x = 0; x < width; ++x) {
220 dst[x] = uint8_t(std::lround(float(sum) * inv));
221 sum += src[wrap(x + radius + 1, width)];
222 sum -= src[wrap(x - radius, width)];
223 }
224 }
225 });
226 // Vertical: one running sum per column, advanced row by row so the reads stay row-major.
227 tbb::parallel_for(tbb::blocked_range<int>(0, width, 256), [&](const tbb::blocked_range<int> &r) {
228 std::vector<uint32_t> sum(size_t(r.size()), 0);
229 for (int k = -radius; k <= radius; ++k) {
230 const uint8_t *row = tmp.data() + size_t(wrap(k, height)) * size_t(width);
231 for (int x = r.begin(); x < r.end(); ++x)
232 sum[size_t(x - r.begin())] += row[x];
233 }
234 for (int y = 0; y < height; ++y) {
235 uint8_t *dst = pixels.data() + size_t(y) * size_t(width);
236 const uint8_t *add = tmp.data() + size_t(wrap(y + radius + 1, height)) * size_t(width);
237 const uint8_t *sub = tmp.data() + size_t(wrap(y - radius, height)) * size_t(width);
238 for (int x = r.begin(); x < r.end(); ++x) {
239 uint32_t &sx = sum[size_t(x - r.begin())];
240 dst[x] = uint8_t(std::lround(float(sx) * inv));
241 sx += add[x];
242 sx -= sub[x];
243 }
244 }
245 });
246 }
247}
SpiralVase::SpiralPoint add(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b)
Definition SpiralVase.cpp:26
coord_t height(const BoundingBox &box)
Definition Arrange.cpp:1185

References smooth_height_pixels_box().

Referenced by smooth_height_pixels(), and smooth_height_pixels_box().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ solve_edge_alignment()

TextureIsland Slic3r::anonymous_namespace{TextureDisplacement.cpp}::solve_edge_alignment ( const Vec2f & rc0,
const Vec2f & rc1,
const Vec2f & pp0,
const Vec2f & pp1,
const Vec2f & cen )
1521{
1522 const Vec2f dP = pp1 - pp0, dC = rc1 - rc0;
1523 const float rho = std::atan2(dP.y(), dP.x()) - std::atan2(dC.y(), dC.x());
1524 const float cs = std::cos(rho), sn = std::sin(rho);
1525 const auto rot = [&](const Vec2f &v) { return Vec2f(v.x() * cs - v.y() * sn, v.x() * sn + v.y() * cs); };
1526
1527 TextureIsland island;
1528 island.scale = 1.f;
1529 island.rotation_deg = rho * 180.f / float(M_PI);
1530 island.offset = pp0 - (rot(rc0 - cen) + cen); // island_transform_matrix places rc0 exactly here
1531 return island;
1532}
Definition TextureDisplacement.hpp:157
Vec2f offset
Definition TextureDisplacement.hpp:158
float rotation_deg
Definition TextureDisplacement.hpp:159
float scale
Definition TextureDisplacement.hpp:163

References M_PI, Slic3r::TextureIsland::offset, Slic3r::TextureIsland::rotation_deg, Slic3r::TextureIsland::scale, and solve_edge_alignment().

Referenced by solve_edge_alignment().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ split_chart_sides()

std::vector< char > Slic3r::anonymous_namespace{TextureDisplacement.cpp}::split_chart_sides ( const indexed_triangle_set & mesh,
const std::vector< Vec3f > & normals,
const std::vector< int > & faces )
986{
987 const auto principal_axis = [](const std::vector<Vec3f> &samples, Vec3f &mean, Vec3f &axis) {
988 mean = Vec3f::Zero();
989 for (const Vec3f &s : samples)
990 mean += s;
991 mean /= float(samples.size());
992 Eigen::Matrix3f covariance = Eigen::Matrix3f::Zero();
993 for (const Vec3f &s : samples) {
994 const Vec3f d = s - mean;
995 covariance += d * d.transpose();
996 }
997 const Eigen::SelfAdjointEigenSolver<Eigen::Matrix3f> solver(covariance);
998 axis = solver.eigenvectors().col(2); // eigenvalues come sorted ascending
999 return solver.eigenvalues()(2) / float(samples.size());
1000 };
1001
1002 std::vector<Vec3f> samples(faces.size());
1003 for (size_t i = 0; i < faces.size(); ++i)
1004 samples[i] = normals[size_t(faces[i])];
1005 Vec3f mean, axis;
1006 if (principal_axis(samples, mean, axis) < 1e-3f) {
1007 for (size_t i = 0; i < faces.size(); ++i) {
1008 const stl_triangle_vertex_indices &tri = mesh.indices[size_t(faces[i])];
1009 samples[i] = (mesh.vertices[tri[0]] + mesh.vertices[tri[1]] + mesh.vertices[tri[2]]) / 3.f;
1010 }
1011 principal_axis(samples, mean, axis);
1012 }
1013 std::vector<char> side(faces.size());
1014 for (size_t i = 0; i < faces.size(); ++i)
1015 side[i] = (samples[i] - mean).dot(axis) > 0.f;
1016 return side;
1017}

References split_chart_sides().

Referenced by segment_into_charts(), and split_chart_sides().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ step_edge_key()

uint64_t Slic3r::anonymous_namespace{TextureDisplacement.cpp}::step_edge_key ( int a,
int b )
inline
4696{
4697 if (a > b) std::swap(a, b);
4698 return (uint64_t(uint32_t(a)) << 32) | uint32_t(b);
4699}

References step_edge_key().

Referenced by scan_steps(), and step_edge_key().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ triangles_overlap()

bool Slic3r::anonymous_namespace{TextureDisplacement.cpp}::triangles_overlap ( const Tri2 & a,
const Tri2 & b,
float eps )
1566{
1567 for (const Tri2 *t : { &a, &b })
1568 for (int i = 0; i < 3; ++i) {
1569 const Vec2f edge = (*t)[(i + 1) % 3] - (*t)[i];
1570 const float len = edge.norm();
1571 if (len < 1e-12f)
1572 continue;
1573 const Vec2f axis(-edge.y() / len, edge.x() / len);
1574 float a_min = std::numeric_limits<float>::max(), a_max = std::numeric_limits<float>::lowest();
1575 float b_min = a_min, b_max = a_max;
1576 for (int k = 0; k < 3; ++k) {
1577 const float pa = axis.dot(a[k]), pb = axis.dot(b[k]);
1578 a_min = std::min(a_min, pa);
1579 a_max = std::max(a_max, pa);
1580 b_min = std::min(b_min, pb);
1581 b_max = std::max(b_max, pb);
1582 }
1583 if (a_max <= b_min + eps || b_max <= a_min + eps)
1584 return false;
1585 }
1586 return true;
1587}
std::array< Vec2f, 3 > Tri2
Definition TextureDisplacement.cpp:1561

References triangles_overlap().

Referenced by Slic3r::anonymous_namespace{TextureDisplacement.cpp}::NetGrid::hits(), and triangles_overlap().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ triplanar_weights()

Vec3f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::triplanar_weights ( const Vec3f & normal)
inline
2199{
2200 Vec3f w = normal.cwiseAbs();
2201 w = w.cwiseProduct(w);
2202 return w.cwiseProduct(w);
2203}

References Slic3r::normal(), and triplanar_weights().

Referenced by triplanar_weights().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ undirected_edge_key()

uint64_t Slic3r::anonymous_namespace{TextureDisplacement.cpp}::undirected_edge_key ( int a,
int b )
913{
914 if (a > b)
915 std::swap(a, b);
916 return (uint64_t(uint32_t(a)) << 32) | uint32_t(b);
917}

References undirected_edge_key().

Referenced by chart_is_flattenable(), segment_into_charts(), and undirected_edge_key().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ unsharp_radius()

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::unsharp_radius ( int width,
int height )
444{
445 return std::max(1.f, 0.002f * float(std::min(width, height)));
446}

References unsharp_radius().

Referenced by adjust_channel_spatial(), and unsharp_radius().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ unwrap_cache_key()

uint64_t Slic3r::anonymous_namespace{TextureDisplacement.cpp}::unwrap_cache_key ( const indexed_triangle_set & patch,
float seam_angle_deg,
float padding_mm,
const std::vector< std::pair< int, int > > & seam_edges )
1203{
1204 uint64_t h = 1469598103934665603ull;
1205 const auto mix = [&h](const void *data, size_t bytes) {
1206 const unsigned char *p = static_cast<const unsigned char *>(data);
1207 for (size_t i = 0; i < bytes; ++i) {
1208 h ^= p[i];
1209 h *= 1099511628211ull;
1210 }
1211 };
1212 mix(patch.vertices.data(), patch.vertices.size() * sizeof(Vec3f));
1213 mix(patch.indices.data(), patch.indices.size() * sizeof(stl_triangle_vertex_indices));
1214 mix(&seam_angle_deg, sizeof(seam_angle_deg));
1215 mix(&padding_mm, sizeof(padding_mm));
1216 mix(seam_edges.data(), seam_edges.size() * sizeof(std::pair<int, int>));
1217 return h;
1218}

References unwrap_cache_key().

Referenced by unwrap_cache_key().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ vec2_from_json()

Vec2f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::vec2_from_json ( const nlohmann::json & j,
const Vec2f & fallback )
5734{
5735 if (!j.is_array() || j.size() != 2)
5736 return fallback;
5737 return Vec2f(j[0].get<float>(), j[1].get<float>());
5738}

References vec2_from_json().

Referenced by vec2_from_json().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ vec2_to_json()

nlohmann::json Slic3r::anonymous_namespace{TextureDisplacement.cpp}::vec2_to_json ( const Vec2f & v)
5730{ return nlohmann::json::array({ v.x(), v.y() }); }

References vec2_to_json().

Referenced by vec2_to_json().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ vec3_from_json()

Vec3f Slic3r::anonymous_namespace{TextureDisplacement.cpp}::vec3_from_json ( const nlohmann::json & j,
const Vec3f & fallback )
5740{
5741 if (!j.is_array() || j.size() != 3)
5742 return fallback;
5743 return Vec3f(j[0].get<float>(), j[1].get<float>(), j[2].get<float>());
5744}

References vec3_from_json().

Referenced by vec3_from_json().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ vec3_to_json()

nlohmann::json Slic3r::anonymous_namespace{TextureDisplacement.cpp}::vec3_to_json ( const Vec3f & v)
5731{ return nlohmann::json::array({ v.x(), v.y(), v.z() }); }

References vec3_to_json().

Referenced by vec3_to_json().

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ g_adjusted_texture_cache

AdjustedTextureCache Slic3r::anonymous_namespace{TextureDisplacement.cpp}::g_adjusted_texture_cache

◆ g_decoded_texture_cache

DecodedTextureCache Slic3r::anonymous_namespace{TextureDisplacement.cpp}::g_decoded_texture_cache

◆ g_smoothed_texture_cache

SmoothedTextureCache Slic3r::anonymous_namespace{TextureDisplacement.cpp}::g_smoothed_texture_cache

◆ g_texture_detail_cache

TextureDetailCache Slic3r::anonymous_namespace{TextureDisplacement.cpp}::g_texture_detail_cache

◆ g_unwrap_cache

UnwrapCache Slic3r::anonymous_namespace{TextureDisplacement.cpp}::g_unwrap_cache

◆ SOURCE_BLUR_MAX_PX

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::SOURCE_BLUR_MAX_PX = 48.f
constexpr

◆ SOURCE_BLUR_SPAN

float Slic3r::anonymous_namespace{TextureDisplacement.cpp}::SOURCE_BLUR_SPAN = 0.05f
constexpr

◆ UNWRAP_CACHE_MAX_ENTRIES

const size_t Slic3r::anonymous_namespace{TextureDisplacement.cpp}::UNWRAP_CACHE_MAX_ENTRIES = 8