OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::TextureBake Namespace Reference

Namespaces

namespace  anonymous_namespace{TextureBakeDecimate.cpp}
namespace  anonymous_namespace{TextureBakeRegularize.cpp}
namespace  anonymous_namespace{TextureBakeRelocate.cpp}
namespace  anonymous_namespace{TextureBakeRepair.cpp}
namespace  anonymous_namespace{TextureBakeSubdivide.cpp}

Classes

struct  DecimateResult
struct  DisplaceSettings
struct  DisplaceBounds
struct  FlipSettings
struct  FlipResult
class  QuantizedPointMap
struct  TriSoup
struct  WeldResult
struct  PipelineSettings
struct  PipelineResult
struct  RegularizeOptions
struct  RegularizeRejectStats
struct  RegularizeResult
struct  RelocateSettings
struct  RelocateResult
struct  EdgeDefects
struct  RepairOptions
struct  VertStore
struct  IndexedMesh
struct  SubdivideResult

Typedefs

using DecimateProgressFn = std::function<bool(double fraction)>
using HeightSampleFn
using DisplaceProgressFn = std::function<bool(double fraction)>
using PipelineProgressFn = std::function<bool(const char *stage, double fraction)>
using ColorSampleFn = std::function<int(const Vec3f &centroid, const Vec3f &normal)>
using SubdivideProgressFn = std::function<bool(double fraction, size_t triangles, double longest_edge)>
using SubdivideWithinFn = std::function<bool(const Vec3d &point, double radius)>

Enumerations

enum class  PipelineMode { Bake , Export }

Functions

DecimateResult decimate (const TriSoup &geometry, size_t target_triangles, bool harvest_flat, double harvest_tol, const std::vector< uint8_t > &locked_faces, const DecimateProgressFn &on_progress, const std::vector< int > &face_color)
TriSoup apply_displacement (const TriSoup &geometry, const HeightSampleFn &sample, const DisplaceSettings &settings, const DisplaceBounds &bounds, const DisplaceProgressFn &on_progress)
FlipResult flip_edges_to_height (const TriSoup &geometry, const std::vector< int > &face_parent_id, const HeightSampleFn &sample, const FlipSettings &settings, const std::vector< uint8_t > &locked)
WeldResult weld_vertices (const std::vector< Vec3f > &positions, double quant)
int64_t grid_round (double v)
TriSoup to_soup (const indexed_triangle_set &its, const std::vector< uint8_t > &face_excluded)
indexed_triangle_set to_indexed_triangle_set (const TriSoup &soup, std::vector< int > *face_color)
void clamp_below_bottom (TriSoup &geometry, float bottom_z)
size_t snap_bottom_to_flat (TriSoup &geometry, float bottom_z, double tol)
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)
RegularizeResult regularize_mesh (const TriSoup &geometry, const std::vector< int > &face_parent_id, double max_edge_length, const RegularizeOptions &opts)
RelocateResult relocate_to_contours (const TriSoup &geometry, const HeightSampleFn &sample, const RelocateSettings &settings, const std::vector< uint8_t > &locked)
EdgeDefects count_edge_defects (const TriSoup &geometry, double quant)
size_t count_area_slivers (const TriSoup &geometry)
TriSoup resolve_t_junctions (const TriSoup &geometry, const RepairOptions &opts, std::vector< int > *face_color)
IndexedMesh to_indexed_fast (const TriSoup &geometry)
IndexedMesh to_indexed (const TriSoup &geometry)
TriSoup to_non_indexed (const VertStore &verts, const std::vector< int > &indices, const std::vector< uint8_t > &face_excluded)
SubdivideResult subdivide (const TriSoup &geometry, double max_edge_length, const std::vector< uint8_t > &face_excluded, bool fast, int safety_cap, const SubdivideProgressFn &on_progress, const SubdivideWithinFn &within)

Variables

static constexpr double DECIMATE_FLIP_DOT = 0.2
static constexpr double DECIMATE_CREASE_COS = 0.5
static constexpr double DECIMATE_CREASE_WEIGHT = 1e4
static constexpr int DECIMATE_MAX_VALENCE = 64
static constexpr double DECIMATE_DEFAULT_HARVEST_TOL = 0.005
static constexpr double WELD_GRID_EXPORT = 1e4
static constexpr double WELD_GRID_GEOMETRY = 1e5
static constexpr double WELD_GRID_DECIMATION = 1e6
static constexpr int FACE_UNPAINTED = -1
static constexpr int FACE_NO_COLOUR = -2
static constexpr int SUBDIVIDE_SAFETY_CAP = 16'000'000
static constexpr double SUBDIVIDE_SHARP_ANGLE_DEG = 30.0
static constexpr int SUBDIVIDE_MAX_ITERATIONS = 12
static constexpr double SUBDIVIDE_GRADE = 1.0

Class Documentation

◆ Slic3r::TextureBake::DecimateResult

struct Slic3r::TextureBake::DecimateResult
Collaboration diagram for Slic3r::TextureBake::DecimateResult:
Class Members
vector< int > face_color
TriSoup geometry
bool locked_over_budget = false
bool target_cost_detail = false

◆ Slic3r::TextureBake::DisplaceSettings

struct Slic3r::TextureBake::DisplaceSettings
Class Members
float amplitude = 0.4f
int blend_normal_smoothing = 32
float bottom_angle_limit = 5.f
float boundary_falloff = 0.f
bool no_downward_z = false
bool symmetric = false
float top_angle_limit = 0.f

◆ Slic3r::TextureBake::DisplaceBounds

struct Slic3r::TextureBake::DisplaceBounds
Class Members
Vec3f max = Vec3f::Zero()
Vec3f min = Vec3f::Zero()

◆ Slic3r::TextureBake::FlipSettings

struct Slic3r::TextureBake::FlipSettings
Class Members
double min_gain_fraction = 0.02
double min_planar_cos = 0.985
int passes = 3

◆ Slic3r::TextureBake::FlipResult

struct Slic3r::TextureBake::FlipResult
Collaboration diagram for Slic3r::TextureBake::FlipResult:
Class Members
vector< int > face_parent_id
size_t flipped = 0
TriSoup geometry

◆ Slic3r::TextureBake::WeldResult

struct Slic3r::TextureBake::WeldResult
Collaboration diagram for Slic3r::TextureBake::WeldResult:
Class Members
int unique_count = 0
vector< int > vertex_id

◆ Slic3r::TextureBake::PipelineSettings

struct Slic3r::TextureBake::PipelineSettings
Collaboration diagram for Slic3r::TextureBake::PipelineSettings:
Class Members
double bottom_snap_tol = 0.1
bool clamp_below_plate = false
DisplaceSettings displace
bool flip_edges = true
FlipSettings flip_opts
bool harvest_flat = true
double harvest_tol = DECIMATE_DEFAULT_HARVEST_TOL
size_t max_triangles = 750'000
SubdivideWithinFn paint_within
function< bool(const Vec3f &centroid)> painted
bool preserve_untextured = true
double refine_length = 1.0
bool regularize = true
RegularizeOptions regularize_opts
double regularize_second_pass_mul = 1.1
bool relocate = false
RelocateSettings relocate_opts
int safety_cap = SUBDIVIDE_SAFETY_CAP

◆ Slic3r::TextureBake::PipelineResult

struct Slic3r::TextureBake::PipelineResult
Collaboration diagram for Slic3r::TextureBake::PipelineResult:
Class Members
bool budget_limited = false
bool canceled = false
size_t collapse_count = 0
vector< int > face_color
vector< int > face_parent_id
TriSoup geometry
bool locked_over_budget = false
bool safety_cap_hit = false
bool simplified = false
size_t triangles_budget = 0
size_t triangles_refined = 0

◆ Slic3r::TextureBake::RegularizeOptions

struct Slic3r::TextureBake::RegularizeOptions
Class Members
double aggressive_normal_delta_cos = 0.906307787037
double aggressive_slack = 8.0
double aspect_threshold = 5.0
double extreme_sliver_aspect = 8.0
double max_normal_delta_cos = 0.965925826289
int maxrounds = 8
bool preserve_excluded = false
double sharp_edge_cos = 0.866025403784
double slack = 3.0

◆ Slic3r::TextureBake::RegularizeRejectStats

struct Slic3r::TextureBake::RegularizeRejectStats
Class Members
size_t degenerate = 0
size_t edge_cap = 0
size_t folded_apex = 0
size_t frozen = 0
size_t link_condition = 0
size_t normal_change = 0
size_t wing_count = 0

◆ Slic3r::TextureBake::RegularizeResult

struct Slic3r::TextureBake::RegularizeResult
Collaboration diagram for Slic3r::TextureBake::RegularizeResult:
Class Members
size_t collapse_count = 0
vector< int > face_parent_id
TriSoup geometry
RegularizeRejectStats reject_stats

◆ Slic3r::TextureBake::RelocateSettings

struct Slic3r::TextureBake::RelocateSettings
Class Members
double contour_level = 0.5
double gradient_step_fraction = 0.25
int iterations = 3
double max_move_fraction = 0.35
double min_gradient_fraction = 0.05

◆ Slic3r::TextureBake::RelocateResult

struct Slic3r::TextureBake::RelocateResult
Collaboration diagram for Slic3r::TextureBake::RelocateResult:
Class Members
TriSoup geometry
size_t moved = 0
size_t rejected = 0

◆ Slic3r::TextureBake::EdgeDefects

struct Slic3r::TextureBake::EdgeDefects
Class Members
size_t non_manifold = 0
size_t open = 0
size_t triangles = 0

◆ Slic3r::TextureBake::RepairOptions

struct Slic3r::TextureBake::RepairOptions
Class Members
int max_iters = 16
double on_seg_tol = 0.02
double weld_quant = WELD_GRID_EXPORT

◆ Slic3r::TextureBake::IndexedMesh

struct Slic3r::TextureBake::IndexedMesh
Collaboration diagram for Slic3r::TextureBake::IndexedMesh:
Class Members
bool has_canon = false
vector< int > indices
QuantizedPointMap pos_canon_map { WELD_GRID_GEOMETRY, 256 }
VertStore verts

◆ Slic3r::TextureBake::SubdivideResult

struct Slic3r::TextureBake::SubdivideResult
Collaboration diagram for Slic3r::TextureBake::SubdivideResult:
Class Members
vector< int > face_parent_id
TriSoup geometry
bool safety_cap_hit = false

Typedef Documentation

◆ ColorSampleFn

using Slic3r::TextureBake::ColorSampleFn = std::function<int(const Vec3f &centroid, const Vec3f &normal)>

◆ DecimateProgressFn

using Slic3r::TextureBake::DecimateProgressFn = std::function<bool(double fraction)>

◆ DisplaceProgressFn

using Slic3r::TextureBake::DisplaceProgressFn = std::function<bool(double fraction)>

◆ HeightSampleFn

Initial value:
std::function<float(const Vec3f &position, const Vec3f &smooth_normal,
const Vec3f &blend_normal)>
Eigen::Matrix< float, 3, 1, Eigen::DontAlign > Vec3f
Definition Point.hpp:65

◆ PipelineProgressFn

using Slic3r::TextureBake::PipelineProgressFn = std::function<bool(const char *stage, double fraction)>

◆ SubdivideProgressFn

using Slic3r::TextureBake::SubdivideProgressFn = std::function<bool(double fraction, size_t triangles, double longest_edge)>

◆ SubdivideWithinFn

using Slic3r::TextureBake::SubdivideWithinFn = std::function<bool(const Vec3d &point, double radius)>

Enumeration Type Documentation

◆ PipelineMode

Enumerator
Bake 
Export 
39{
40 // Keeps the face-parent mapping; skips decimation and repair.
41 Bake,
42 // The full sequence, including decimation and repair.
43 Export,
44};
@ Export
Definition TextureBakePipeline.hpp:43
@ Bake
Definition TextureBakePipeline.hpp:41

Function Documentation

◆ apply_displacement()

TriSoup Slic3r::TextureBake::apply_displacement ( const TriSoup & geometry,
const HeightSampleFn & sample,
const DisplaceSettings & settings,
const DisplaceBounds & bounds,
const DisplaceProgressFn & on_progress )
23{
24 TriSoup out;
25 const size_t count = geometry.pos.size();
26 if (count == 0 || !sample)
27 return geometry;
28
29 out.pos.resize(count);
30 out.nrm.resize(count);
31
32 // Everything below is keyed by this id, which is what makes one vector per position expressible.
33 const bool need_id_positions = settings.boundary_falloff > 0.f;
34 QuantizedPointMap dedup(WELD_GRID_GEOMETRY, std::min(count, size_t(1) << 22));
35 std::vector<int> vertex_id(count);
36 std::vector<Vec3f> id_pos;
37 int next_id = 0;
38 for (size_t i = 0; i < count; ++i) {
39 const int id = dedup.get_or_set(geometry.pos[i], next_id);
40 if (dedup.inserted()) {
41 ++next_id;
42 if (need_id_positions)
43 id_pos.push_back(geometry.pos[i]);
44 }
45 vertex_id[i] = id;
46 }
47 const size_t unique_count = size_t(next_id);
48
49 // Pass 1: area-weighted smooth normals per position, plus what masking and falloff need.
50 std::vector<Vec3d> smooth_nrm(unique_count, Vec3d::Zero());
51 std::vector<double> masked_area(unique_count, 0.0), total_area(unique_count, 0.0);
52 const bool have_weights = !geometry.exclude_weight.empty();
53 std::vector<uint8_t> user_excluded_face(have_weights ? count / 3 : 0, 0);
54 std::vector<uint8_t> excluded_pos(have_weights ? unique_count : 0, 0);
55
56 for (size_t t = 0; t + 2 < count; t += 3) {
57 const Vec3d a = geometry.pos[t].cast<double>();
58 const Vec3d face_n = (geometry.pos[t + 1].cast<double>() - a).cross(geometry.pos[t + 2].cast<double>() - a);
59 const double face_area = face_n.norm(); // twice the triangle area, so weighting is natural
60 const double nz = face_area > 1e-12 ? face_n.z() / face_area : 0.0;
61 const double face_angle = std::acos(std::min(1.0, std::abs(nz))) * (180.0 / M_PI);
62 const bool angle_masked =
63 nz < 0.0 ? (settings.bottom_angle_limit > 0.f && face_angle <= settings.bottom_angle_limit)
64 : (settings.top_angle_limit > 0.f && face_angle <= settings.top_angle_limit);
65
66 // Thresholded high, not at a half: merging by maximum leaves a face bordering an excluded one
67 // with two corners at 1.0, averaging about 0.67, which a half threshold would misread.
68 bool user_excluded = false;
69 if (have_weights) {
70 const float avg = (geometry.exclude_weight[t] + geometry.exclude_weight[t + 1] +
71 geometry.exclude_weight[t + 2]) / 3.f;
72 user_excluded = avg > 0.99f;
73 if (user_excluded)
74 user_excluded_face[t / 3] = 1;
75 }
76
77 for (int v = 0; v < 3; ++v) {
78 const size_t vid = size_t(vertex_id[t + size_t(v)]);
79 if (user_excluded && have_weights)
80 excluded_pos[vid] = 1;
81 // Subdivision split vertices at sharp edges, so these are smooth across soft edges and
82 // sharp across hard ones - no faceting on round surfaces, no rounding of corners.
83 smooth_nrm[vid] += geometry.nrm[t + size_t(v)].cast<double>() * face_area;
84 if (angle_masked)
85 masked_area[vid] += face_area;
86 total_area[vid] += face_area;
87 }
88 }
89
90 // The pre-normalisation magnitude over the total area says how much the neighbouring faces agree:
91 // near 1 they do, near 0 they cancelled, meaning a knife edge with no usable surface direction.
92 std::vector<double> reliability(unique_count, 0.0);
93 for (size_t id = 0; id < unique_count; ++id) {
94 const double len = smooth_nrm[id].norm();
95 reliability[id] = (len > 0.0 && total_area[id] > 0.0) ? len / total_area[id] : 0.0;
96 smooth_nrm[id] = (len > 0.0) ? Vec3d(smooth_nrm[id] / len) : Vec3d(0.0, 0.0, 1.0);
97 }
98
99 // Pass 1.5: the smoothed blend normal - see the header for why it is separate.
100 std::vector<Vec3d> blend_nrm = smooth_nrm;
101 if (settings.blend_normal_smoothing > 0 && unique_count > 0) {
102 // CSR adjacency over the welded graph, deliberately a multigraph: duplicates weight a pair by
103 // how often it shares an edge, so a well-connected surface couples more strongly.
104 std::vector<uint32_t> degree(unique_count, 0);
105 const auto add_degree = [&](int a, int b) {
106 if (a != b) { ++degree[size_t(a)]; ++degree[size_t(b)]; }
107 };
108 for (size_t t = 0; t + 2 < count; t += 3) {
109 const int a = vertex_id[t], b = vertex_id[t + 1], c = vertex_id[t + 2];
110 add_degree(a, b); add_degree(b, c); add_degree(c, a);
111 }
112 std::vector<uint32_t> csr_start(unique_count + 1, 0);
113 for (size_t id = 0; id < unique_count; ++id)
114 csr_start[id + 1] = csr_start[id] + degree[id];
115 std::vector<uint32_t> neighbors(csr_start[unique_count]);
116 std::vector<uint32_t> cursor(unique_count, 0);
117 const auto add_edge = [&](int a, int b) {
118 if (a == b)
119 return;
120 neighbors[csr_start[size_t(a)] + cursor[size_t(a)]++] = uint32_t(b);
121 neighbors[csr_start[size_t(b)] + cursor[size_t(b)]++] = uint32_t(a);
122 };
123 for (size_t t = 0; t + 2 < count; t += 3) {
124 const int a = vertex_id[t], b = vertex_id[t + 1], c = vertex_id[t + 2];
125 add_edge(a, b); add_edge(b, c); add_edge(c, a);
126 }
127
128 std::vector<Vec3d> cur = smooth_nrm, nxt(unique_count, Vec3d::Zero());
129 for (int iter = 0; iter < settings.blend_normal_smoothing; ++iter) {
130 // Jacobi, so every vertex reads the previous iteration and the rows are independent.
131 tbb::parallel_for(tbb::blocked_range<size_t>(0, unique_count, 4096), [&](const tbb::blocked_range<size_t> &r) {
132 for (size_t id = r.begin(); id < r.end(); ++id) {
133 const uint32_t s = csr_start[id], e = csr_start[id + 1];
134 if (e == s) {
135 nxt[id] = cur[id];
136 continue;
137 }
138 Vec3d sum = Vec3d::Zero();
139 for (uint32_t k = s; k < e; ++k)
140 sum += cur[neighbors[k]];
141 sum /= double(e - s);
142 const double len = sum.norm();
143 // Cancelling neighbours mean a knife edge; keep what we had.
144 nxt[id] = (len > 1e-12) ? Vec3d(sum / len) : cur[id];
145 }
146 });
147 cur.swap(nxt);
148 }
149 blend_nrm = std::move(cur);
150 }
151
152 // A boundary position borders both masked and unmasked faces, or sits on the exclusion seam.
153 // Every other position gets its distance to the nearest one, ramped to 1 at the falloff distance.
154 std::vector<double> falloff;
155 if (settings.boundary_falloff > 0.f && unique_count > 0) {
156 std::vector<Vec3f> boundary;
157 for (size_t id = 0; id < unique_count; ++id) {
158 const double frac = total_area[id] > 0.0 ? masked_area[id] / total_area[id] : 0.0;
159 const bool on_excl = !excluded_pos.empty() && excluded_pos[id] != 0;
160 if (on_excl || (frac > 0.0 && frac < 1.0))
161 boundary.push_back(id_pos[id]);
162 }
163 falloff.assign(unique_count, 1.0);
164 if (!boundary.empty()) {
165 // A uniform grid: the query is nearest-point only, so a tree costs more than it saves.
166 Vec3f lo = boundary.front(), hi = boundary.front();
167 for (const Vec3f &p : boundary) {
168 lo = lo.cwiseMin(p);
169 hi = hi.cwiseMax(p);
170 }
171 const Vec3f span = (hi - lo).cwiseMax(Vec3f(1e-6f, 1e-6f, 1e-6f));
172 const int res = std::clamp(int(std::ceil(std::cbrt(double(boundary.size())) * 2.0)), 4, 128);
173 const Vec3f cell = span / float(res);
174 const float cell_min = cell.minCoeff();
175 const auto cell_of = [&](const Vec3f &p) {
176 Vec3i32 c;
177 for (int k = 0; k < 3; ++k)
178 c[k] = std::clamp(int((p[k] - lo[k]) / span[k] * float(res)), 0, res - 1);
179 return c;
180 };
181 const auto cell_index = [&](int x, int y, int z) {
182 return size_t(z) * size_t(res) * size_t(res) + size_t(y) * size_t(res) + size_t(x);
183 };
184 std::vector<std::vector<int>> grid(size_t(res) * size_t(res) * size_t(res));
185 for (size_t i = 0; i < boundary.size(); ++i) {
186 const Vec3i32 c = cell_of(boundary[i]);
187 grid[cell_index(c.x(), c.y(), c.z())].push_back(int(i));
188 }
189
190 const double radius = double(settings.boundary_falloff);
191 for (size_t id = 0; id < unique_count; ++id) {
192 const Vec3f &p = id_pos[id];
193 const Vec3i32 c = cell_of(p);
194 double best = std::numeric_limits<double>::max();
195 // Anything in shell r is at least (r - 1) cells away, so once the best found is within
196 // that bound nothing closer can be hiding further out.
197 for (int r = 0; r < res; ++r) {
198 for (int dz = -r; dz <= r; ++dz)
199 for (int dy = -r; dy <= r; ++dy)
200 for (int dx = -r; dx <= r; ++dx) {
201 // The shell only; its interior was covered by a smaller r.
202 if (r > 0 && std::abs(dx) != r && std::abs(dy) != r && std::abs(dz) != r)
203 continue;
204 const int qx = c.x() + dx, qy = c.y() + dy, qz = c.z() + dz;
205 if (qx < 0 || qy < 0 || qz < 0 || qx >= res || qy >= res || qz >= res)
206 continue;
207 for (const int bi : grid[cell_index(qx, qy, qz)])
208 best = std::min(best, double((boundary[size_t(bi)] - p).norm()));
209 }
210 if (best <= double(r) * double(cell_min))
211 break;
212 }
213 falloff[id] = (best == std::numeric_limits<double>::max() || radius <= 0.0)
214 ? 1.0
215 : std::clamp(best / radius, 0.0, 1.0);
216 }
217 }
218 }
219
220 // Pass 2: one sample per unique position. A representative corner is picked first so the sampling
221 // itself is a flat parallel loop - it is a texture fetch plus projection maths per layer, and by
222 // far the most expensive thing in this stage.
223 std::vector<double> grey(unique_count, 0.0);
224 std::vector<int> representative(unique_count, -1);
225 for (size_t i = 0; i < count; ++i)
226 if (representative[size_t(vertex_id[i])] < 0)
227 representative[size_t(vertex_id[i])] = int(i);
228 tbb::parallel_for(tbb::blocked_range<size_t>(0, unique_count),
229 [&](const tbb::blocked_range<size_t> &range) {
230 for (size_t vid = range.begin(); vid < range.end(); ++vid) {
231 const int rep = representative[vid];
232 if (rep < 0)
233 continue;
234 grey[vid] = double(sample(geometry.pos[size_t(rep)],
235 smooth_nrm[vid].cast<float>(),
236 blend_nrm[vid].cast<float>()));
237 }
238 });
239
240 // Pass 3: move every copy of a position by the identical vector. Each iteration writes only its
241 // own output slot, so the loop is independent per corner.
242 tbb::parallel_for(tbb::blocked_range<size_t>(0, count), [&](const tbb::blocked_range<size_t> &range) {
243 for (size_t i = range.begin(); i < range.end(); ++i) {
244 const Vec3f &p = geometry.pos[i];
245 const size_t vid = size_t(vertex_id[i]);
246
247 // Only angle masking uses the per-position blend, so an excluded face never dims its
248 // neighbours through a shared vertex.
249 const bool face_excluded = !user_excluded_face.empty() && user_excluded_face[i / 3] != 0;
250 // Pinned where an included face shares a position with an excluded one, sealing the boundary.
251 const bool sealed_boundary =
252 !face_excluded && !excluded_pos.empty() && excluded_pos[vid] != 0;
253 const double masked_frac = total_area[vid] > 0.0 ? masked_area[vid] / total_area[vid] : 0.0;
254 const double centered = settings.symmetric ? (grey[vid] - 0.5) : grey[vid];
255 const double ramp = falloff.empty() ? 1.0 : falloff[vid];
256 const double disp = (face_excluded || sealed_boundary)
257 ? 0.0
258 : ramp * (1.0 - masked_frac) * centered * double(settings.amplitude);
259
260 Vec3d moved = p.cast<double>() + smooth_nrm[vid] * disp;
261
262 // Stop a partly masked vertex poking through the surface it borders.
263 if (masked_frac > 0.0) {
264 if (settings.bottom_angle_limit > 0.f && moved.z() < double(p.z())) moved.z() = double(p.z());
265 if (settings.top_angle_limit > 0.f && moved.z() > double(p.z())) moved.z() = double(p.z());
266 }
267 if (settings.no_downward_z && moved.z() < double(p.z()))
268 moved.z() = double(p.z());
269 // A vertex starting on the bottom plane stays there: otherwise a downward-facing face pulls
270 // *up* where the sample is below mid-grey, leaving bed-contact vertices at differing heights.
271 if (settings.no_downward_z && double(p.z()) <= double(bounds.min.z()) + 1e-5)
272 moved.z() = double(p.z());
273
274 out.pos[i] = moved.cast<float>();
275 }
276 });
277
278 // Per-face, not averaged across shared positions: averaging can flip an excluded face's normal
279 // when its neighbours moved outward.
280 tbb::parallel_for(tbb::blocked_range<size_t>(0, count / 3), [&](const tbb::blocked_range<size_t> &r) {
281 for (size_t f = r.begin(); f < r.end(); ++f) {
282 const size_t t = f * 3;
283 const Vec3f n = (out.pos[t + 1] - out.pos[t]).cross(out.pos[t + 2] - out.pos[t]).normalized();
284 out.nrm[t] = out.nrm[t + 1] = out.nrm[t + 2] = n;
285 }
286 });
287 out.exclude_weight = geometry.exclude_weight;
288 return out;
289}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
#define M_PI
Definition ExtrusionSimulator.cpp:31
Definition TextureBakeIndex.hpp:39
T clamp(T a, T lower, T upper)
Definition Bicubic.hpp:186
bool no_downward_z
Definition TextureBakeDisplace.hpp:41
int blend_normal_smoothing
Definition TextureBakeDisplace.hpp:51
bool symmetric
Definition TextureBakeDisplace.hpp:33
float bottom_angle_limit
Definition TextureBakeDisplace.hpp:37
float boundary_falloff
Definition TextureBakeDisplace.hpp:44
float top_angle_limit
Definition TextureBakeDisplace.hpp:38
static constexpr double WELD_GRID_GEOMETRY
Definition TextureBakeIndex.hpp:25
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition Point.hpp:68
auto range(Cont &&cont)
Definition libslic3r.h:405
void bounds(const NSVGimage &image, Vec2f &min, Vec2f &max)
Definition NSVGUtils.cpp:91
T cross(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:168
std::vector< ArithmeticOnly< T > > grid(const T &start, const T &stop, const T &stride)
A set of equidistant values starting from 'start' (inclusive), ending in the closest multiple of 'str...
Definition MTUtils.hpp:127
static double f(double x, double z_sin, double z_cos, bool vertical, bool flip)
Definition FillGyroid.cpp:124
Eigen::Matrix< int32_t, 3, 1, Eigen::DontAlign > Vec3i32
Definition Point.hpp:59
Definition TextureBakeIndex.hpp:142
std::vector< Vec3f > pos
Definition TextureBakeIndex.hpp:143
std::vector< float > exclude_weight
Definition TextureBakeIndex.hpp:145
std::vector< Vec3f > nrm
Definition TextureBakeIndex.hpp:144

References Slic3r::TextureBake::DisplaceSettings::amplitude, Slic3r::TextureBake::DisplaceSettings::blend_normal_smoothing, Slic3r::TextureBake::DisplaceSettings::bottom_angle_limit, Slic3r::TextureBake::DisplaceSettings::boundary_falloff, Slic3r::bounds(), count, Slic3r::cross(), Slic3r::TextureBake::TriSoup::exclude_weight, Slic3r::f(), Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::grid(), Slic3r::TextureBake::QuantizedPointMap::inserted(), M_PI, Slic3r::TextureBake::DisplaceSettings::no_downward_z, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::TriSoup::pos, Slic3r::range(), Slic3r::TextureBake::DisplaceSettings::symmetric, Slic3r::TextureBake::DisplaceSettings::top_angle_limit, and WELD_GRID_GEOMETRY.

Referenced by run_pipeline().

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

◆ clamp_below_bottom()

void Slic3r::TextureBake::clamp_below_bottom ( TriSoup & geometry,
float bottom_z )
34{
35 for (size_t t = 0; t + 2 < geometry.pos.size(); t += 3) {
36 bool dirty = false;
37 for (int k = 0; k < 3; ++k)
38 if (geometry.pos[t + size_t(k)].z() < bottom_z) {
39 geometry.pos[t + size_t(k)].z() = bottom_z;
40 dirty = true;
41 }
42 if (!dirty)
43 continue;
44 Vec3f n = (geometry.pos[t + 1] - geometry.pos[t]).cross(geometry.pos[t + 2] - geometry.pos[t]);
45 const float len = n.norm();
46 n = (len > 0.f) ? Vec3f(n / len) : Vec3f(0.f, 0.f, 1.f);
47 geometry.nrm[t] = geometry.nrm[t + 1] = geometry.nrm[t + 2] = n;
48 }
49}

References Slic3r::cross(), Slic3r::TextureBake::TriSoup::nrm, and Slic3r::TextureBake::TriSoup::pos.

Referenced by run_pipeline().

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

◆ count_area_slivers()

size_t Slic3r::TextureBake::count_area_slivers ( const TriSoup & geometry)
63{
64 size_t n = 0;
65 for (size_t t = 0; t + 2 < geometry.pos.size(); t += 3) {
66 const Vec3d u = (geometry.pos[t + 1] - geometry.pos[t]).cast<double>();
67 const Vec3d v = (geometry.pos[t + 2] - geometry.pos[t]).cast<double>();
68 // The threshold a slicer applies: area below 1e-12 mm^2.
69 if (u.cross(v).squaredNorm() < 1e-24)
70 ++n;
71 }
72 return n;
73}
auto cast(const BoundingBoxBase< Tin > &b)
Definition BoundingBox.hpp:299

References Slic3r::cast(), and Slic3r::TextureBake::TriSoup::pos.

Here is the call graph for this function:

◆ count_edge_defects()

EdgeDefects Slic3r::TextureBake::count_edge_defects ( const TriSoup & geometry,
double quant )
33{
34 EdgeDefects out;
35 const size_t n = geometry.pos.size();
36 out.triangles = n / 3;
37 QuantizedPointMap vmap(quant, std::min(n, size_t(1) << 22));
38 std::vector<int> id(n);
39 int next = 0;
40 for (size_t i = 0; i < n; ++i) {
41 id[i] = vmap.get_or_set(geometry.pos[i], next);
42 if (vmap.inserted())
43 ++next;
44 }
45 std::unordered_map<uint64_t, int> counts;
46 for (size_t t = 0; t + 2 < n; t += 3) {
47 const int a = id[t], b = id[t + 1], c = id[t + 2];
48 if (a == b || b == c || a == c)
49 continue;
50 const int tri[3] = { a, b, c };
51 for (int e = 0; e < 3; ++e)
52 ++counts[edge_key(tri[e], tri[(e + 1) % 3])];
53 }
54 for (const auto &[key, c] : counts) {
55 (void) key;
56 if (c == 1) ++out.open;
57 else if (c > 2) ++out.non_manifold;
58 }
59 return out;
60}
size_t non_manifold
Definition TextureBakeRepair.hpp:21
size_t triangles
Definition TextureBakeRepair.hpp:21
size_t open
Definition TextureBakeRepair.hpp:21
Definition TextureBakeRepair.hpp:20

References Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::EdgeDefects::non_manifold, Slic3r::TextureBake::EdgeDefects::open, Slic3r::TextureBake::TriSoup::pos, and Slic3r::TextureBake::EdgeDefects::triangles.

Here is the call graph for this function:

◆ decimate()

DecimateResult Slic3r::TextureBake::decimate ( const TriSoup & geometry,
size_t target_triangles,
bool harvest_flat,
double harvest_tol,
const std::vector< uint8_t > & locked_faces,
const DecimateProgressFn & on_progress,
const std::vector< int > & face_color )
108{
109 DecimateResult result;
110 const size_t n = geometry.pos.size();
111 if (n < 3) {
112 result.geometry = geometry;
113 return result;
114 }
115
116 // The finest grid. Anything coarser fuses distinct fine-feature vertices on a displaced mesh,
117 // leaving it non-manifold before decimation starts and producing open edges afterwards.
118 QuantizedPointMap vert_map(WELD_GRID_DECIMATION, std::min(n, size_t(1) << 22));
119 std::vector<Vec3d> pos;
120 std::vector<int> remap(n);
121 for (size_t i = 0; i < n; ++i) {
122 const int idx = vert_map.get_or_set(geometry.pos[i], int(pos.size()));
123 if (vert_map.inserted())
124 pos.push_back(geometry.pos[i].cast<double>());
125 remap[i] = idx;
126 }
127 const size_t vert_count = pos.size();
128 const size_t face_count = n / 3;
129 std::vector<int> faces(face_count * 3);
130 for (size_t i = 0; i < n; ++i)
131 faces[i] = remap[i];
132
133 if (face_count <= target_triangles && !harvest_flat) {
134 result.geometry = geometry;
135 return result;
136 }
137
138 // An edge with a locked endpoint never reaches the heap.
139 std::vector<uint8_t> locked_vert;
140 size_t locked_face_count = 0;
141 if (!locked_faces.empty()) {
142 locked_vert.assign(vert_count, 0);
143 for (size_t f = 0; f < face_count && f < locked_faces.size(); ++f) {
144 if (!locked_faces[f])
145 continue;
146 ++locked_face_count;
147 for (int k = 0; k < 3; ++k)
148 locked_vert[size_t(faces[f * 3 + size_t(k)])] = 1;
149 }
150 }
151 // With the locked faces alone at the target, chasing it would grind the free region to its guard
152 // limit for nothing - harvest only, and say so.
153 const bool locked_over_budget =
154 !locked_vert.empty() && face_count > target_triangles && locked_face_count >= target_triangles;
155 result.locked_over_budget = locked_over_budget;
156 if (locked_over_budget && !harvest_flat) {
157 result.geometry = geometry;
158 return result;
159 }
160
161 std::vector<Quadric> quadrics(vert_count);
162 {
163 // The plane per face is independent; accumulating it into the three incident vertices is not,
164 // so only the first half is parallel.
165 std::vector<Vec4d> planes(face_count, Vec4d::Zero());
166 tbb::parallel_for(tbb::blocked_range<size_t>(0, face_count),
167 [&](const tbb::blocked_range<size_t> &range) {
168 for (size_t f = range.begin(); f < range.end(); ++f) {
169 const int a = faces[f * 3], b = faces[f * 3 + 1], c = faces[f * 3 + 2];
170 if (a < 0)
171 continue;
172 const Vec3d nrm = face_normal_unit(pos, a, b, c);
173 if (nrm.isZero())
174 continue;
175 planes[f] = Vec4d(nrm.x(), nrm.y(), nrm.z(), -nrm.dot(pos[size_t(a)]));
176 }
177 });
178 for (size_t f = 0; f < face_count; ++f) {
179 const Vec4d &pl = planes[f];
180 if (pl.head<3>().isZero())
181 continue;
182 for (int k = 0; k < 3; ++k)
183 quadrics[size_t(faces[f * 3 + size_t(k)])].add_plane(pl.x(), pl.y(), pl.z(), pl.w());
184 }
185 }
186
187 // Two penalty planes per endpoint on a sharp interior edge, each perpendicular to one adjacent
188 // face and containing the edge, constraining the vertex to the crease line.
189 {
190 struct EdgeRec { int va, vb, f0, f1; uint8_t count; };
191 std::vector<EdgeRec> edges;
192 QuantizedPointMap edge_idx(1.0, std::min(face_count * 3, size_t(1) << 22));
193 for (size_t f = 0; f < face_count; ++f) {
194 if (faces[f * 3] < 0)
195 continue;
196 for (int e = 0; e < 3; ++e) {
197 const int va = faces[f * 3 + size_t(e)];
198 const int vb = faces[f * 3 + size_t((e + 1) % 3)];
199 const int lo = std::min(va, vb), hi = std::max(va, vb);
200 const int ei = edge_idx.get_or_set_key(lo, hi, 0, int(edges.size()));
201 if (edge_idx.inserted())
202 edges.push_back({ lo, hi, int(f), -1, 1 });
203 else if (edges[size_t(ei)].count == 1) {
204 edges[size_t(ei)].f1 = int(f);
205 edges[size_t(ei)].count = 2;
206 } else
207 // Non-manifold; never feeds a crease.
208 edges[size_t(ei)].count = 3;
209 }
210 }
211
212 const double sqrt_w = std::sqrt(DECIMATE_CREASE_WEIGHT);
213 for (const EdgeRec &er : edges) {
214 if (er.count != 2)
215 continue; // boundary or non-manifold
216 const Vec3d n0 = face_normal_unit(pos, faces[size_t(er.f0) * 3], faces[size_t(er.f0) * 3 + 1],
217 faces[size_t(er.f0) * 3 + 2]);
218 const Vec3d n1 = face_normal_unit(pos, faces[size_t(er.f1) * 3], faces[size_t(er.f1) * 3 + 1],
219 faces[size_t(er.f1) * 3 + 2]);
220 const bool color_edge = face_color.size() > std::max(size_t(er.f0), size_t(er.f1)) &&
221 face_color[size_t(er.f0)] != face_color[size_t(er.f1)];
222 if (!color_edge && n0.dot(n1) >= DECIMATE_CREASE_COS)
223 continue; // smooth enough to be no crease, and no colour changes across it
224
225 const Vec3d e = pos[size_t(er.vb)] - pos[size_t(er.va)];
226 const double elen = e.norm();
227 if (elen <= 0.0)
228 continue;
229 const Vec3d ed = e / elen;
230 for (const Vec3d &fn : { n0, n1 }) {
231 Vec3d pn = fn.cross(ed);
232 const double plen = pn.norm();
233 if (plen < 1e-10)
234 continue; // edge parallel to the face normal
235 pn /= plen;
236 const double d = -pn.dot(pos[size_t(er.va)]);
237 // sqrt(w) on the inputs gives w times the accumulated products.
238 for (const int v : { er.va, er.vb })
239 quadrics[size_t(v)].add_plane(pn.x() * sqrt_w, pn.y() * sqrt_w, pn.z() * sqrt_w,
240 d * sqrt_w);
241 }
242 }
243 }
244
245 // Vertex-face incidence as intrusive linked lists of slots over flat arrays, with each list's length.
246 const size_t S = face_count * 3;
247 std::vector<int> vf_head(vert_count, -1), vf_count(vert_count, 0), slot_face(S), slot_vert(S),
248 slot_next(S, -1), slot_prev(S, -1), face_slot(S, -1);
249 for (size_t f = 0; f < face_count; ++f)
250 for (int k = 0; k < 3; ++k) {
251 const int s = int(f) * 3 + k;
252 const int v = faces[size_t(s)];
253 slot_face[size_t(s)] = int(f);
254 slot_vert[size_t(s)] = v;
255 slot_next[size_t(s)] = vf_head[size_t(v)];
256 slot_prev[size_t(s)] = -1;
257 if (vf_head[size_t(v)] >= 0)
258 slot_prev[size_t(vf_head[size_t(v)])] = s;
259 vf_head[size_t(v)] = s;
260 face_slot[size_t(s)] = s;
261 ++vf_count[size_t(v)];
262 }
263 const auto unlink_slot = [&](int s) {
264 const int p = slot_prev[size_t(s)], nx = slot_next[size_t(s)], v = slot_vert[size_t(s)];
265 if (p >= 0) slot_next[size_t(p)] = nx;
266 else vf_head[size_t(v)] = nx;
267 if (nx >= 0) slot_prev[size_t(nx)] = p;
268 --vf_count[size_t(v)];
269 };
270 const auto move_slot = [&](int s, int nv) {
271 unlink_slot(s);
272 slot_next[size_t(s)] = vf_head[size_t(nv)];
273 slot_prev[size_t(s)] = -1;
274 if (vf_head[size_t(nv)] >= 0)
275 slot_prev[size_t(vf_head[size_t(nv)])] = s;
276 vf_head[size_t(nv)] = s;
277 slot_vert[size_t(s)] = nv;
278 ++vf_count[size_t(nv)];
279 };
280
281 std::vector<uint8_t> active(vert_count, 1);
282 std::vector<uint32_t> version(vert_count, 0);
283 std::vector<uint32_t> nb_stamp(vert_count, 0), lk_stamp(vert_count, 0);
284 uint32_t epoch = 1, lk_epoch = 1;
285 size_t active_faces = face_count;
286
287 // A plain vector driven by the heap algorithms, so the capacity can be reserved. Lazy deletion
288 // means roughly one entry per edge plus one per re-push after each collapse; the reserve below is
289 // sized from the edge count and simply grows if a mesh needs more.
290 std::vector<HeapEntry> heap;
291 heap.reserve(std::min<size_t>(face_count * 3, size_t(1) << 24));
292 const auto heap_push = [&](HeapEntry e) {
293 heap.push_back(e);
294 std::push_heap(heap.begin(), heap.end(), std::greater<HeapEntry>());
295 };
296 const auto heap_pop = [&]() {
297 std::pop_heap(heap.begin(), heap.end(), std::greater<HeapEntry>());
298 const HeapEntry e = heap.back();
299 heap.pop_back();
300 return e;
301 };
302 size_t pops = 0, stale_pops = 0, compactions = 0;
303
304 // An entry is stale once either endpoint has been removed or moved by a later collapse.
305 const auto is_stale = [&](const HeapEntry &e) {
306 return !active[size_t(e.v1)] || !active[size_t(e.v2)] || version[size_t(e.v1)] != e.ver1 ||
307 version[size_t(e.v2)] != e.ver2;
308 };
309 // Measured on a 2.4 M -> 750 k run, 82% of pops were stale: every collapse re-pushes the survivor's
310 // edges and orphans the old ones, so the heap grows to several times the live edge set and every
311 // sift walks that much further through memory. Dropping the dead entries and re-heapifying once
312 // they dominate costs one linear pass, amortised against the growth that triggered it.
313 //
314 // Keyed to the live face count rather than to the heap's own size: lazy popping keeps the heap from
315 // ever doubling, but the live edge set (about 1.5 per face) shrinks as decimation proceeds, so by the
316 // end the heap is several times what is still collapsible. Compact once it passes twice that.
317 const auto maybe_compact = [&]() {
318 if (heap.size() < std::max<size_t>(size_t(1) << 16, active_faces * 3))
319 return;
320 heap.erase(std::remove_if(heap.begin(), heap.end(), is_stale), heap.end());
321 std::make_heap(heap.begin(), heap.end(), std::greater<HeapEntry>());
322 ++compactions;
323 };
324
325 // Where an edge collapses to. Also re-run on pop instead of stored - see HeapEntry.
326 const auto collapse_target = [&](int v1, int v2) -> Vec3d {
327 Vec3d p;
328 if (!solve_q(quadrics, v1, v2, p)) {
329 const Vec3d mid = (pos[size_t(v1)] + pos[size_t(v2)]) * 0.5;
330 const double e1 = eval_sum(quadrics, v1, v2, pos[size_t(v1)]);
331 const double e2 = eval_sum(quadrics, v1, v2, pos[size_t(v2)]);
332 const double em = eval_sum(quadrics, v1, v2, mid);
333 const double emin = std::min({ e1, e2, em });
334 const double etol = emin * 1e-2 + 1e-12;
335 // The midpoint when the three are near-equal, i.e. flat: it moves adjacent triangles
336 // least, so fewer normal flips and no stalling on coplanar geometry.
337 if (em <= emin + etol) p = mid;
338 else if (e1 <= e2) p = pos[size_t(v1)];
339 else p = pos[size_t(v2)];
340 }
341 return p;
342 };
343 const auto push_edge = [&](int v1, int v2) {
344 const Vec3d p = collapse_target(v1, v2);
345 // The cost is evaluated at the exact target and the collapse moves to its float rounding -
346 // the same split as when the rounded target was stored in the entry, so no ordering changes.
347 // Where quadric costs are all near zero, shorter edges first keeps triangle quality up.
348 const double len2 = (pos[size_t(v2)] - pos[size_t(v1)]).squaredNorm();
349 heap_push({ eval_sum(quadrics, v1, v2, p) + len2 * 1e-8, v1, v2, version[size_t(v1)],
350 version[size_t(v2)] });
351 };
352
353 {
354 QuantizedPointMap seed_seen(1.0, std::min(face_count * 3, size_t(1) << 22));
355 for (size_t f = 0; f < face_count; ++f) {
356 if (faces[f * 3] < 0)
357 continue;
358 for (int e = 0; e < 3; ++e) {
359 const int va = faces[f * 3 + size_t(e)];
360 const int vb = faces[f * 3 + size_t((e + 1) % 3)];
361 if (!locked_vert.empty() && (locked_vert[size_t(va)] || locked_vert[size_t(vb)]))
362 continue;
363 seed_seen.get_or_set_key(std::min(va, vb), std::max(va, vb), 0, 1);
364 if (seed_seen.inserted())
365 push_edge(va, vb);
366 }
367 }
368 }
369
370 // 0 means a stale entry, 1 a boundary edge, 2 or more safe.
371 const auto shared_face_count = [&](int v1, int v2) {
372 int count = 0;
373 for (int s = vf_head[size_t(v1)]; s >= 0; s = slot_next[size_t(s)]) {
374 const int f = slot_face[size_t(s)];
375 if (faces[size_t(f) * 3] < 0)
376 continue;
377 for (int k = 0; k < 3; ++k)
378 if (faces[size_t(f) * 3 + size_t(k)] == v2) {
379 if (++count >= 2)
380 return 2;
381 break;
382 }
383 }
384 return count;
385 };
386
387 // Safe only when the sole common neighbours of the endpoints are the apexes of the faces the edge
388 // already shares; any other would pile a third triangle onto an edge after the collapse.
389 const auto has_link_violation = [&](int v1, int v2, uint32_t ep) {
390 for (int s = vf_head[size_t(v1)]; s >= 0; s = slot_next[size_t(s)]) {
391 const int f = slot_face[size_t(s)];
392 if (faces[size_t(f) * 3] < 0)
393 continue;
394 for (int k = 0; k < 3; ++k)
395 if (const int x = faces[size_t(f) * 3 + size_t(k)]; x != v1)
396 lk_stamp[size_t(x)] = ep;
397 }
398 int shared = 0;
399 for (int s = vf_head[size_t(v1)]; s >= 0; s = slot_next[size_t(s)]) {
400 const int f = slot_face[size_t(s)];
401 if (faces[size_t(f) * 3] < 0)
402 continue;
403 const int a = faces[size_t(f) * 3], b = faces[size_t(f) * 3 + 1], c = faces[size_t(f) * 3 + 2];
404 if (a == v2 || b == v2 || c == v2) {
405 ++shared;
406 const int apex = (a != v1 && a != v2) ? a : (b != v1 && b != v2) ? b : c;
407 lk_stamp[size_t(apex)] = ep + 1; // a legal shared-face apex
408 }
409 }
410 if (shared > 2)
411 return true; // already non-manifold
412 for (int s = vf_head[size_t(v2)]; s >= 0; s = slot_next[size_t(s)]) {
413 const int f = slot_face[size_t(s)];
414 if (faces[size_t(f) * 3] < 0)
415 continue;
416 for (int k = 0; k < 3; ++k) {
417 const int x = faces[size_t(f) * 3 + size_t(k)];
418 if (x != v2 && x != v1 && lk_stamp[size_t(x)] == ep)
419 return true;
420 }
421 }
422 return false;
423 };
424
425 // Squared-dot, so no square root or division. Faces containing the other endpoint are the ones
426 // being removed, so they are skipped.
427 const auto check_flipped = [&](int vc, int vo, const Vec3d &np) {
428 for (int s = vf_head[size_t(vc)]; s >= 0; s = slot_next[size_t(s)]) {
429 const size_t f = size_t(slot_face[size_t(s)]);
430 if (faces[f * 3] < 0)
431 continue;
432 const int fa = faces[f * 3], fb = faces[f * 3 + 1], fc = faces[f * 3 + 2];
433 if (fa == vo || fb == vo || fc == vo)
434 continue;
435 const Vec3d oa = pos[size_t(fa)], ob = pos[size_t(fb)], oc = pos[size_t(fc)];
436 const Vec3d on = (ob - oa).cross(oc - oa);
437 const Vec3d na = (fa == vc) ? np : oa;
438 const Vec3d nb = (fb == vc) ? np : ob;
439 const Vec3d nc = (fc == vc) ? np : oc;
440 const Vec3d nn = (nb - na).cross(nc - na);
441 const double raw = on.dot(nn);
442 if (raw < 0.0)
443 return true;
444 if (raw * raw < DECIMATE_FLIP_DOT * DECIMATE_FLIP_DOT * on.squaredNorm() * nn.squaredNorm())
445 return true;
446 }
447 return false;
448 };
449
450 const size_t init_faces = active_faces;
451 const size_t to_remove = std::max<size_t>(1, init_faces > target_triangles
452 ? init_faces - target_triangles : init_faces);
453 const double harvest_ceil = harvest_tol * harvest_tol;
454 bool reached_target = locked_over_budget;
455 double last_progress = 0.0;
456
457 while (!heap.empty()) {
458 if (active_faces <= target_triangles) {
459 if (!harvest_flat)
460 break;
461 reached_target = true;
462 }
463
464 const HeapEntry top = heap_pop();
465 ++pops;
466 if (on_progress) {
467 // Every 16 k pops as well as whenever the fraction moves: this is the only place a cancel
468 // is seen, and past the target the fraction stops moving.
469 const double p = std::min(1.0, double(init_faces - active_faces) / double(to_remove));
470 if (p - last_progress > 0.005 || (pops & 0x3fff) == 0) {
471 last_progress = p;
472 if (!on_progress(p))
473 break;
474 }
475 }
476 // The popped entry is the cheapest left, so exceeding the tolerance ends the run.
477 if (reached_target && top.cost > harvest_ceil)
478 break;
479
480 const int v1 = top.v1, v2 = top.v2;
481 if (is_stale(top)) {
482 ++stale_pops;
483 continue;
484 }
485 // Ahead of the checks below, which walk the fans (see DECIMATE_MAX_VALENCE). The two shared
486 // faces, counted in both fans, go.
487 if (vf_count[size_t(v1)] + vf_count[size_t(v2)] - 4 > DECIMATE_MAX_VALENCE)
488 continue;
489 if (shared_face_count(v1, v2) < 2)
490 continue;
491 lk_epoch += 2; // +2 so ep and ep+1 cannot collide with the next call
492 if (has_link_violation(v1, v2, lk_epoch))
493 continue;
494 const Vec3d target = collapse_target(v1, v2).cast<float>().cast<double>();
495 if (check_flipped(v1, v2, target) || check_flipped(v2, v1, target))
496 continue;
497 if (!reached_target && top.cost > harvest_ceil)
498 result.target_cost_detail = true;
499
500 // v1 survives at the new position, v2 goes.
501 pos[size_t(v1)] = target;
502 quadrics[size_t(v1)] += quadrics[size_t(v2)];
503 ++version[size_t(v1)];
504
505 for (int s = vf_head[size_t(v2)]; s >= 0;) {
506 const size_t f = size_t(slot_face[size_t(s)]);
507 const int s_next = slot_next[size_t(s)]; // read before the list is modified
508 if (faces[f * 3] >= 0) {
509 for (int k = 0; k < 3; ++k)
510 if (faces[f * 3 + size_t(k)] == v2) {
511 faces[f * 3 + size_t(k)] = v1;
512 break;
513 }
514 const int fa = faces[f * 3], fb = faces[f * 3 + 1], fc = faces[f * 3 + 2];
515 if (fa == fb || fb == fc || fa == fc) {
516 for (int k = 0; k < 3; ++k)
517 if (const int sk = face_slot[f * 3 + size_t(k)]; sk >= 0) {
518 unlink_slot(sk);
519 face_slot[f * 3 + size_t(k)] = -1;
520 }
521 faces[f * 3] = faces[f * 3 + 1] = faces[f * 3 + 2] = -1;
522 --active_faces;
523 } else
524 move_slot(s, v1);
525 }
526 s = s_next;
527 }
528 active[size_t(v2)] = 0;
529
530 ++epoch;
531 for (int sv = vf_head[size_t(v1)]; sv >= 0; sv = slot_next[size_t(sv)]) {
532 const size_t f = size_t(slot_face[size_t(sv)]);
533 if (faces[f * 3] < 0)
534 continue;
535 for (int k = 0; k < 3; ++k) {
536 const int nb = faces[f * 3 + size_t(k)];
537 if (nb == v1 || nb_stamp[size_t(nb)] == epoch)
538 continue;
539 nb_stamp[size_t(nb)] = epoch;
540 // v1 is never locked - a locked edge never entered the heap.
541 if (active[size_t(nb)] && (locked_vert.empty() || !locked_vert[size_t(nb)]))
542 push_edge(v1, nb);
543 }
544 }
545 maybe_compact();
546 }
547
548 BOOST_LOG_TRIVIAL(info) << "TextureBake decimate: pops=" << pops << " stale=" << stale_pops
549 << " compactions=" << compactions << " heap_peak=" << heap.capacity()
550 << " faces=" << active_faces;
551
552 // Rebuild from the surviving faces, with per-face normals.
553 TriSoup &out = result.geometry;
554 if (!face_color.empty())
555 result.face_color.reserve(active_faces);
556 for (size_t f = 0; f < face_count; ++f) {
557 if (faces[f * 3] < 0)
558 continue;
559 if (!face_color.empty())
560 result.face_color.push_back(f < face_color.size() ? face_color[f] : -1);
561 const Vec3f a = pos[size_t(faces[f * 3])].cast<float>();
562 const Vec3f b = pos[size_t(faces[f * 3 + 1])].cast<float>();
563 const Vec3f c = pos[size_t(faces[f * 3 + 2])].cast<float>();
564 const Vec3f nrm = (b - a).cross(c - a).normalized();
565 out.pos.insert(out.pos.end(), { a, b, c });
566 out.nrm.insert(out.nrm.end(), { nrm, nrm, nrm });
567 }
568 return result;
569}
Vec3d face_normal_unit(const std::vector< Vec3d > &pos, int a, int b, int c)
Definition TextureBakeDecimate.cpp:80
bool solve_q(const std::vector< Quadric > &qs, int v1, int v2, Vec3d &out)
Definition TextureBakeDecimate.cpp:56
double eval_sum(const std::vector< Quadric > &qs, int v1, int v2, const Vec3d &p)
Definition TextureBakeDecimate.cpp:50
static constexpr double DECIMATE_CREASE_WEIGHT
Definition TextureBakeDecimate.hpp:28
static constexpr int DECIMATE_MAX_VALENCE
Definition TextureBakeDecimate.hpp:33
TriSoup geometry
Definition TextureBakeDecimate.hpp:48
static constexpr double DECIMATE_FLIP_DOT
Definition TextureBakeDecimate.hpp:24
std::vector< int > face_color
Definition TextureBakeDecimate.hpp:55
bool target_cost_detail
Definition TextureBakeDecimate.hpp:61
static constexpr double WELD_GRID_DECIMATION
Definition TextureBakeIndex.hpp:26
bool locked_over_budget
Definition TextureBakeDecimate.hpp:58
static constexpr double DECIMATE_CREASE_COS
Definition TextureBakeDecimate.hpp:26
Definition TextureBakeDecimate.hpp:47
Vec3d pos(const Pt &p)
Definition ReprojectPointsOnMesh.hpp:16
Eigen::Matrix< double, 4, 1, Eigen::DontAlign > Vec4d
Definition Point.hpp:69

References Slic3r::cast(), count, Slic3r::cross(), DECIMATE_CREASE_COS, DECIMATE_CREASE_WEIGHT, DECIMATE_FLIP_DOT, DECIMATE_MAX_VALENCE, Slic3r::f(), Slic3r::TextureBake::DecimateResult::face_color, Slic3r::TextureBake::DecimateResult::geometry, Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::QuantizedPointMap::get_or_set_key(), Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::DecimateResult::locked_over_budget, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::TriSoup::pos, Slic3r::range(), Slic3r::TextureBake::DecimateResult::target_cost_detail, and WELD_GRID_DECIMATION.

Referenced by run_pipeline().

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

◆ flip_edges_to_height()

FlipResult Slic3r::TextureBake::flip_edges_to_height ( const TriSoup & geometry,
const std::vector< int > & face_parent_id,
const HeightSampleFn & sample,
const FlipSettings & settings,
const std::vector< uint8_t > & locked )
25{
26 FlipResult result;
27 result.geometry = geometry;
28 result.face_parent_id = face_parent_id;
29 const size_t count = geometry.pos.size();
30 const size_t tri_ct = count / 3;
31 if (count == 0 || !sample || settings.passes <= 0)
32 return result;
33
34 // Weld, so a flip rewrites triangles in terms of shared vertices rather than positions.
35 QuantizedPointMap weld(WELD_GRID_GEOMETRY, std::min(count, size_t(1) << 22));
36 std::vector<int> vid(count);
37 std::vector<Vec3f> pos;
38 std::vector<float> weight; // exclude weight per unique vertex, the max over its copies
39 const bool has_weight = !geometry.exclude_weight.empty();
40 for (size_t i = 0; i < count; ++i) {
41 vid[i] = weld.get_or_set(geometry.pos[i], int(pos.size()));
42 if (weld.inserted()) {
43 pos.push_back(geometry.pos[i]);
44 weight.push_back(has_weight ? geometry.exclude_weight[i] : 0.f);
45 } else if (has_weight) {
46 weight[size_t(vid[i])] = std::max(weight[size_t(vid[i])], geometry.exclude_weight[i]);
47 }
48 }
49 const size_t nv = pos.size();
50
51 // Triangles as vertex ids; a locked or excluded triangle never takes part.
52 std::vector<std::array<int, 3>> tri(tri_ct);
53 std::vector<uint8_t> fixed(tri_ct, 0);
54 for (size_t t = 0; t < tri_ct; ++t) {
55 tri[t] = { vid[t * 3], vid[t * 3 + 1], vid[t * 3 + 2] };
56 if (!locked.empty() && t < locked.size() && locked[t])
57 fixed[t] = 1;
58 if (has_weight && geometry.exclude_weight[t * 3] > 0.99f)
59 fixed[t] = 1;
60 }
61
62 // Height per unique vertex along its area-weighted normal, the direction displacement will use.
63 std::vector<Vec3f> nrm(nv, Vec3f::Zero());
64 std::vector<Vec3f> face_n(tri_ct);
65 const auto rebuild_normals = [&]() {
66 std::fill(nrm.begin(), nrm.end(), Vec3f::Zero());
67 for (size_t t = 0; t < tri_ct; ++t) {
68 const Vec3f fn = (pos[size_t(tri[t][1])] - pos[size_t(tri[t][0])]).cross(pos[size_t(tri[t][2])] - pos[size_t(tri[t][0])]);
69 face_n[t] = fn;
70 for (int k = 0; k < 3; ++k)
71 nrm[size_t(tri[t][size_t(k)])] += fn;
72 }
73 for (Vec3f &n : nrm) {
74 const float l = n.norm();
75 n = (l > 0.f) ? Vec3f(n / l) : Vec3f(0.f, 0.f, 1.f);
76 }
77 };
78 rebuild_normals();
79 std::vector<float> h(nv, 0.f);
80 tbb::parallel_for(tbb::blocked_range<size_t>(0, nv), [&](const tbb::blocked_range<size_t> &r) {
81 for (size_t v = r.begin(); v < r.end(); ++v)
82 h[v] = sample(pos[v], nrm[v], nrm[v]);
83 });
84 float h_lo = std::numeric_limits<float>::max(), h_hi = -h_lo;
85 for (const float x : h) { h_lo = std::min(h_lo, x); h_hi = std::max(h_hi, x); }
86 const float range = h_hi - h_lo;
87 if (!(range > 0.f))
88 return result; // flat: every diagonal is as good as the other
89 const float min_gain = float(settings.min_gain_fraction) * range;
90 const float planar = float(settings.min_planar_cos);
91
92 struct Candidate
93 {
94 uint32_t t1, t2; // the two triangles
95 uint8_t k1, k2; // corner index in each where the shared edge starts (t1: a->c, t2: c->a)
96 float gain;
97 };
98
99 for (int pass = 0; pass < settings.passes; ++pass) {
100 // Half-edges keyed by their undirected edge, sorted so the two halves of an interior edge land
101 // next to each other; a run of exactly two with opposite directions is a manifold interior
102 // edge. Sorting beats hashing here by an order of magnitude on a few million triangles.
103 struct Half { uint64_t key; uint32_t corner; };
104 std::vector<Half> half;
105 half.reserve(count);
106 for (size_t t = 0; t < tri_ct; ++t)
107 for (int k = 0; k < 3; ++k) {
108 const int from = tri[t][size_t(k)], to = tri[t][size_t((k + 1) % 3)];
109 if (from == to) continue;
110 const uint32_t lo = uint32_t(std::min(from, to)), hi = uint32_t(std::max(from, to));
111 half.push_back({ (uint64_t(lo) << 32) | hi, uint32_t(t * 3 + size_t(k)) });
112 }
113 tbb::parallel_sort(half.begin(), half.end(), [](const Half &x, const Half &y) {
114 return x.key != y.key ? x.key < y.key : x.corner < y.corner;
115 });
116 std::vector<std::pair<uint32_t, uint32_t>> edges; // (corner in t1, corner in t2), t1 < t2
117 edges.reserve(half.size() / 2);
118 for (size_t i = 0; i < half.size();) {
119 size_t j = i + 1;
120 while (j < half.size() && half[j].key == half[i].key) ++j;
121 if (j - i == 2) {
122 // Opposite directions: the lower vertex id is `from` in exactly one of the two.
123 const uint32_t c1 = half[i].corner, c2 = half[i + 1].corner;
124 const int f1 = tri[c1 / 3][size_t(c1 % 3)], f2 = tri[c2 / 3][size_t(c2 % 3)];
125 if (f1 != f2)
126 edges.emplace_back(c1, c2);
127 }
128 i = j;
129 }
130 std::vector<Candidate> cands(edges.size());
131 std::vector<uint8_t> valid(edges.size(), 0);
132 tbb::parallel_for(tbb::blocked_range<size_t>(0, edges.size()), [&](const tbb::blocked_range<size_t> &r) {
133 for (size_t i = r.begin(); i < r.end(); ++i) {
134 const uint32_t c1 = uint32_t(edges[i].first), c2 = uint32_t(edges[i].second);
135 const uint32_t t1 = c1 / 3, t2 = c2 / 3;
136 const int k1 = int(c1 % 3), k2 = int(c2 % 3);
137 if (fixed[t1] || fixed[t2]) continue;
138 if (!face_parent_id.empty() && face_parent_id[t1] != face_parent_id[t2]) continue;
139 // a->c is the shared edge in t1, with b opposite; t2 runs c->a with d opposite.
140 const int a = tri[t1][size_t(k1)], c = tri[t1][size_t((k1 + 1) % 3)], b = tri[t1][size_t((k1 + 2) % 3)];
141 const int d = tri[t2][size_t((k2 + 2) % 3)];
142 if (b == d) continue;
143 // Level quads have nothing to gain; the test is on the corners, before any sampling.
144 const float hmin = std::min({ h[size_t(a)], h[size_t(b)], h[size_t(c)], h[size_t(d)] });
145 const float hmax = std::max({ h[size_t(a)], h[size_t(b)], h[size_t(c)], h[size_t(d)] });
146 if (hmax - hmin < min_gain) continue;
147 // Coplanar enough to have a real alternative, and convex so the alternative is valid:
148 // the new triangles (a, d, b) and (d, c, b) must both face the way the quad does, with
149 // a decent share of its area.
150 const Vec3f n1 = face_n[t1], n2 = face_n[t2];
151 const float l1 = n1.norm(), l2 = n2.norm();
152 if (l1 <= 0.f || l2 <= 0.f || n1.dot(n2) < planar * l1 * l2) continue;
153 const Vec3f quad_n = (n1 + n2).normalized();
154 const Vec3f &pa = pos[size_t(a)], &pb = pos[size_t(b)], &pc = pos[size_t(c)], &pd = pos[size_t(d)];
155 const Vec3f m1 = (pd - pa).cross(pb - pa), m2 = (pc - pd).cross(pb - pd);
156 const float area_old = l1 + l2, area_new = m1.dot(quad_n) + m2.dot(quad_n);
157 const float min_part = 0.05f * area_old;
158 if (m1.dot(quad_n) < min_part || m2.dot(quad_n) < min_part) continue;
159 if (std::abs(area_new - area_old) > 0.02f * area_old) continue; // not the same quad: folded
160 // The diagonals' midpoint errors.
161 const auto mid_err = [&](int u, int w) {
162 const Vec3f p = 0.5f * (pos[size_t(u)] + pos[size_t(w)]);
163 Vec3f n = nrm[size_t(u)] + nrm[size_t(w)];
164 const float l = n.norm();
165 n = (l > 0.f) ? Vec3f(n / l) : quad_n;
166 return std::abs(sample(p, n, n) - 0.5f * (h[size_t(u)] + h[size_t(w)]));
167 };
168 const float gain = mid_err(a, c) - mid_err(b, d);
169 if (gain < min_gain) continue;
170 cands[i] = { t1, t2, uint8_t(k1), uint8_t(k2), gain };
171 valid[i] = 1;
172 }
173 });
174 std::vector<Candidate> chosen;
175 for (size_t i = 0; i < cands.size(); ++i)
176 if (valid[i]) chosen.push_back(cands[i]);
177 std::sort(chosen.begin(), chosen.end(), [](const Candidate &x, const Candidate &y) {
178 return x.gain != y.gain ? x.gain > y.gain : (x.t1 != y.t1 ? x.t1 < y.t1 : x.t2 < y.t2);
179 });
180 // Best first, and a triangle changes at most once per pass.
181 std::vector<uint8_t> touched(tri_ct, 0);
182 size_t applied = 0;
183 for (const Candidate &cd : chosen) {
184 if (touched[cd.t1] || touched[cd.t2]) continue;
185 const int a = tri[cd.t1][cd.k1], c = tri[cd.t1][size_t((cd.k1 + 1) % 3)], b = tri[cd.t1][size_t((cd.k1 + 2) % 3)];
186 const int d = tri[cd.t2][size_t((cd.k2 + 2) % 3)];
187 tri[cd.t1] = { a, d, b };
188 tri[cd.t2] = { d, c, b };
189 touched[cd.t1] = touched[cd.t2] = 1;
190 ++applied;
191 }
192 result.flipped += applied;
193 if (applied == 0)
194 break;
195 rebuild_normals(); // face normals feed the planarity test of the next pass
196 }
197
198 if (result.flipped == 0)
199 return result;
200
201 // Back to the soup: positions, weights and per-face normals from the (possibly rewritten) triangles.
202 for (size_t t = 0; t < tri_ct; ++t) {
203 for (int k = 0; k < 3; ++k) {
204 const size_t i = t * 3 + size_t(k);
205 const int v = tri[t][size_t(k)];
206 result.geometry.pos[i] = pos[size_t(v)];
207 if (has_weight)
208 result.geometry.exclude_weight[i] = weight[size_t(v)];
209 }
210 Vec3f n = (result.geometry.pos[t * 3 + 1] - result.geometry.pos[t * 3]).cross(result.geometry.pos[t * 3 + 2] - result.geometry.pos[t * 3]);
211 const float len = n.norm();
212 n = (len > 0.f) ? Vec3f(n / len) : Vec3f(0.f, 0.f, 1.f);
213 result.geometry.nrm[t * 3] = result.geometry.nrm[t * 3 + 1] = result.geometry.nrm[t * 3 + 2] = n;
214 }
215 return result;
216}
TriSoup geometry
Definition TextureBakeFlip.hpp:46
double min_gain_fraction
Definition TextureBakeFlip.hpp:37
std::vector< int > face_parent_id
Definition TextureBakeFlip.hpp:47
int passes
Definition TextureBakeFlip.hpp:33
double min_planar_cos
Definition TextureBakeFlip.hpp:41
size_t flipped
Definition TextureBakeFlip.hpp:48
Definition TextureBakeFlip.hpp:45

References count, Slic3r::cross(), Slic3r::TextureBake::TriSoup::exclude_weight, Slic3r::TextureBake::FlipResult::face_parent_id, Slic3r::TextureBake::FlipResult::flipped, Slic3r::TextureBake::FlipResult::geometry, Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::FlipSettings::min_gain_fraction, Slic3r::TextureBake::FlipSettings::min_planar_cos, Slic3r::TextureBake::FlipSettings::passes, Slic3r::TextureBake::TriSoup::pos, Slic3r::range(), and WELD_GRID_GEOMETRY.

Referenced by run_pipeline().

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

◆ grid_round()

int64_t Slic3r::TextureBake::grid_round ( double v)
inline
30{ return int64_t(std::floor(v + 0.5)); }

Referenced by Slic3r::TextureBake::QuantizedPointMap::get(), Slic3r::TextureBake::QuantizedPointMap::get_or_set(), and resolve_t_junctions().

Here is the caller graph for this function:

◆ regularize_mesh()

RegularizeResult Slic3r::TextureBake::regularize_mesh ( const TriSoup & geometry,
const std::vector< int > & face_parent_id,
double max_edge_length,
const RegularizeOptions & opts )
58{
59 RegularizeResult result;
60 const size_t tri_count = geometry.triangle_count();
61 if (tri_count == 0 || max_edge_length <= 0.0) {
62 result.geometry = geometry;
63 result.face_parent_id = face_parent_id;
64 return result;
65 }
66
67 const double base_max_len_sq = (max_edge_length * opts.slack) * (max_edge_length * opts.slack);
68 const double aggr_max_len_sq =
69 (max_edge_length * opts.aggressive_slack) * (max_edge_length * opts.aggressive_slack);
70 const double extreme_aspect2 = opts.extreme_sliver_aspect * opts.extreme_sliver_aspect;
71 const double aspect_thr2 = opts.aspect_threshold * opts.aspect_threshold;
72
73 // Double precision: a collapse writes a midpoint back and later collapses read it, so rounding
74 // would accumulate.
75 QuantizedPointMap pos_map(WELD_GRID_GEOMETRY, std::min(tri_count * 3, size_t(1) << 22));
76 std::vector<Vec3d> vert;
77 std::vector<int> corners(tri_count * 3);
78 vert.reserve(tri_count);
79 for (size_t i = 0; i < tri_count * 3; ++i) {
80 const Vec3f &p = geometry.pos[i];
81 const int id = pos_map.get_or_set(p, int(vert.size()));
82 if (pos_map.inserted())
83 vert.push_back(p.cast<double>());
84 corners[i] = id;
85 }
86 const size_t vert_count = vert.size();
87
88 std::vector<Vec3d> tri_nrm(tri_count, Vec3d::Zero());
89 std::vector<uint8_t> tri_deleted(tri_count, 0);
90 result.face_parent_id = face_parent_id;
91 if (result.face_parent_id.size() != tri_count)
92 result.face_parent_id.assign(tri_count, 0);
93
94 const auto sq_dist = [&](int a, int b) { return (vert[size_t(a)] - vert[size_t(b)]).squaredNorm(); };
95
96 const auto recompute_face_normal = [&](size_t t) {
97 const Vec3d &a = vert[size_t(corners[t * 3])];
98 const Vec3d n = (vert[size_t(corners[t * 3 + 1])] - a).cross(vert[size_t(corners[t * 3 + 2])] - a);
99 const double len = n.norm();
100 tri_nrm[t] = (len > 0.0) ? Vec3d(n / len) : Vec3d::Zero();
101 };
102 for (size_t t = 0; t < tri_count; ++t)
103 recompute_face_normal(t);
104
105 // Never updated - the normal gate measures against these, so drift cannot compound across rounds.
106 const std::vector<Vec3d> orig_nrm = tri_nrm;
107
108 // Squared thinness, the longest edge over the shortest altitude:
109 // thinness = lmax / hmin = lmax^2 / (2 * area), so thinness^2 = lmax^4 / |AB x AC|^2
110 //
111 // Not lmax/lmin, which misses what matters here: three near-collinear points can have all edges
112 // similar, so an edge ratio reports about 2 and the gate skips a triangle with near-zero area
113 // whose corners sample three unrelated texels. An equilateral scores about 1.15.
114 const auto tri_aspect_sq = [&](size_t t) -> double {
115 const Vec3d &a = vert[size_t(corners[t * 3])];
116 const Vec3d ab = vert[size_t(corners[t * 3 + 1])] - a;
117 const Vec3d ac = vert[size_t(corners[t * 3 + 2])] - a;
118 const Vec3d bc = vert[size_t(corners[t * 3 + 2])] - vert[size_t(corners[t * 3 + 1])];
119 const double lmax2 = std::max({ ab.squaredNorm(), ac.squaredNorm(), bc.squaredNorm() });
120 const double cross2 = ab.cross(ac).squaredNorm();
121 return cross2 > 0.0 ? lmax2 * lmax2 / cross2 : std::numeric_limits<double>::infinity();
122 };
123
124 SlotLists slots;
125 slots.init(vert_count, tri_count * 3);
126 for (size_t s = 0; s < tri_count * 3; ++s)
127 slots.link(int(s), corners);
128
129 // O(1) membership without clearing a set per collapse.
130 std::vector<uint32_t> vert_stamp(vert_count, 0), tri_stamp(tri_count, 0);
131 uint32_t stamp_gen = 0;
132
133 // Both endpoints of a hard edge are barred from being collapse endpoints, preserving such
134 // corners exactly while leaving flat-face interiors free.
135 //
136 // Skipped when either triangle is an extreme sliver: a sliver's normal is dominated by where its
137 // far apex sits, so noise pivots it tens of degrees with no feature behind it, and freezing on
138 // that would lock the very chains this pass exists to dissolve. Genuine features are bordered by
139 // well-shaped triangles and are unaffected.
140 std::vector<uint8_t> frozen_vert(vert_count, 0);
141 {
142 std::vector<double> tri_thin2(tri_count);
143 for (size_t t = 0; t < tri_count; ++t)
144 tri_thin2[t] = tri_aspect_sq(t);
145 QuantizedPointMap edge_seen(1.0, std::min(tri_count * 3, size_t(1) << 22));
146 for (size_t t = 0; t < tri_count; ++t)
147 for (int e = 0; e < 3; ++e) {
148 const int u = corners[t * 3 + size_t(e)];
149 const int v = corners[t * 3 + size_t((e + 1) % 3)];
150 const int lo = std::min(u, v), hi = std::max(u, v);
151 const int other = edge_seen.get_or_set_key(lo, hi, 0, int(t));
152 if (edge_seen.inserted())
153 continue;
154 if (tri_thin2[t] > extreme_aspect2 || tri_thin2[size_t(other)] > extreme_aspect2)
155 continue;
156 if (tri_nrm[t].dot(tri_nrm[size_t(other)]) < opts.sharp_edge_cos) {
157 frozen_vert[size_t(u)] = 1;
158 frozen_vert[size_t(v)] = 1;
159 }
160 }
161 }
162
163 // Exclusion freeze. The weight is constant across a face's corners, so the first one answers.
164 if (opts.preserve_excluded && !geometry.exclude_weight.empty())
165 for (size_t t = 0; t < tri_count; ++t)
166 if (geometry.exclude_weight[t * 3] > 0.99f)
167 for (int k = 0; k < 3; ++k)
168 frozen_vert[size_t(corners[t * 3 + size_t(k)])] = 1;
169
170 std::vector<int> wing_scratch, affected_scratch;
171
172 const auto third_vertex = [&](size_t t, int u, int v) {
173 const int a = corners[t * 3], b = corners[t * 3 + 1], c = corners[t * 3 + 2];
174 if (a != u && a != v) return a;
175 if (b != u && b != v) return b;
176 return c;
177 };
178 const auto triangles_sharing_edge = [&](int u, int v) -> std::vector<int> & {
179 wing_scratch.clear();
180 for (int s = slots.head[size_t(u)]; s != -1; s = slots.next[size_t(s)]) {
181 const size_t t = size_t(s) / 3;
182 if (tri_deleted[t])
183 continue;
184 if (corners[t * 3] == v || corners[t * 3 + 1] == v || corners[t * 3 + 2] == v)
185 wing_scratch.push_back(int(t));
186 }
187 return wing_scratch;
188 };
189
190 RegularizeRejectStats &stats = result.reject_stats;
191
192 const auto try_collapse = [&](int u, int v) -> bool {
193 if (u == v)
194 return false;
195 if (frozen_vert[size_t(u)] || frozen_vert[size_t(v)]) { ++stats.frozen; return false; }
196
197 // Two wings means a manifold interior edge.
198 std::vector<int> &wings = triangles_sharing_edge(u, v);
199 if (wings.size() != 2) { ++stats.wing_count; return false; }
200 const size_t w0 = size_t(wings[0]), w1 = size_t(wings[1]);
201 const int apex1 = third_vertex(w0, u, v), apex2 = third_vertex(w1, u, v);
202 if (apex1 == apex2) { ++stats.folded_apex; return false; }
203
204 // The edge cap loosens if *either* wing is extreme, since the re-subdivision recovers an
205 // over-long edge. The normal cap needs *both*, which is what protects fillets.
206 const double w1a = tri_aspect_sq(w0), w2a = tri_aspect_sq(w1);
207 const bool either_extreme = w1a > extreme_aspect2 || w2a > extreme_aspect2;
208 const bool both_extreme = w1a > extreme_aspect2 && w2a > extreme_aspect2;
209 const double eff_max_len_sq = either_extreme ? aggr_max_len_sq : base_max_len_sq;
210 const double eff_normal_cos =
211 both_extreme ? opts.aggressive_normal_delta_cos : opts.max_normal_delta_cos;
212
213 // A vertex sharing a triangle with both endpoints, other than the wing apexes, would go
214 // non-manifold. Stamp one side's neighbours, scan the other against them.
215 ++stamp_gen;
216 for (int s = slots.head[size_t(v)]; s != -1; s = slots.next[size_t(s)]) {
217 const size_t t = size_t(s) / 3;
218 if (tri_deleted[t])
219 continue;
220 for (int k = 0; k < 3; ++k)
221 if (const int x = corners[t * 3 + size_t(k)]; x != v)
222 vert_stamp[size_t(x)] = stamp_gen;
223 }
224 for (int s = slots.head[size_t(u)]; s != -1; s = slots.next[size_t(s)]) {
225 const size_t t = size_t(s) / 3;
226 if (tri_deleted[t])
227 continue;
228 for (int k = 0; k < 3; ++k) {
229 const int x = corners[t * 3 + size_t(k)];
230 if (x != u && x != v && x != apex1 && x != apex2 && vert_stamp[size_t(x)] == stamp_gen) {
231 ++stats.link_condition;
232 return false;
233 }
234 }
235 }
236
237 const Vec3d m = (vert[size_t(u)] + vert[size_t(v)]) * 0.5;
238
239 // Everything using either endpoint; the wings are being deleted.
240 ++stamp_gen;
241 affected_scratch.clear();
242 for (const int endpoint : { u, v })
243 for (int s = slots.head[size_t(endpoint)]; s != -1; s = slots.next[size_t(s)]) {
244 const size_t t = size_t(s) / 3;
245 if (tri_deleted[t] || t == w0 || t == w1)
246 continue;
247 if (tri_stamp[t] != stamp_gen) {
248 tri_stamp[t] = stamp_gen;
249 affected_scratch.push_back(int(t));
250 }
251 }
252
253 // Validate every affected triangle before touching anything.
254 for (const int ti : affected_scratch) {
255 const size_t t = size_t(ti);
256 Vec3d p[3];
257 for (int k = 0; k < 3; ++k) {
258 const int x = corners[t * 3 + size_t(k)];
259 p[k] = (x == u || x == v) ? m : vert[size_t(x)];
260 }
261 const double ab2 = (p[1] - p[0]).squaredNorm();
262 const double bc2 = (p[2] - p[1]).squaredNorm();
263 const double ca2 = (p[0] - p[2]).squaredNorm();
264 if (ab2 > eff_max_len_sq || bc2 > eff_max_len_sq || ca2 > eff_max_len_sq) {
265 ++stats.edge_cap;
266 return false;
267 }
268 const Vec3d n = (p[1] - p[0]).cross(p[2] - p[0]);
269 const double nlen = n.norm();
270 if (nlen <= 0.0) { ++stats.degenerate; return false; }
271 if ((n / nlen).dot(orig_nrm[t]) < eff_normal_cos) { ++stats.normal_change; return false; }
272 }
273
274 // Apply: move u to the merged position and redirect every reference to v.
275 vert[size_t(u)] = m;
276 for (const size_t w : { w0, w1 }) {
277 tri_deleted[w] = 1;
278 for (int k = 0; k < 3; ++k)
279 slots.unlink(int(w * 3) + k, corners);
280 }
281 // A non-wing triangle contains v exactly once, so moving its slots suffices.
282 for (int s = slots.head[size_t(v)]; s != -1;) {
283 const int ns = slots.next[size_t(s)];
284 slots.unlink(s, corners);
285 corners[size_t(s)] = u;
286 slots.link(s, corners);
287 recompute_face_normal(size_t(s) / 3);
288 s = ns;
289 }
290 for (int s = slots.head[size_t(u)]; s != -1; s = slots.next[size_t(s)]) {
291 const size_t t = size_t(s) / 3;
292 if (!tri_deleted[t])
293 recompute_face_normal(t);
294 }
295 return true;
296 };
297
298 // Per-triangle thinness for a round's candidate scan; -1 marks "not a candidate". Scored in
299 // parallel, since the scan only reads the mesh, then gathered serially in index order.
300 std::vector<double> round_aspect(tri_count);
301 for (int round = 0; round < opts.maxrounds; ++round) {
302 // Rebuilt each round so earlier collapses inform the priorities.
303 tbb::parallel_for(tbb::blocked_range<size_t>(0, tri_count), [&](const tbb::blocked_range<size_t> &r) {
304 for (size_t t = r.begin(); t < r.end(); ++t) {
305 round_aspect[t] = -1.0;
306 if (tri_deleted[t])
307 continue;
308 const int a = corners[t * 3], b = corners[t * 3 + 1], c = corners[t * 3 + 2];
309 if (std::min({ sq_dist(a, b), sq_dist(b, c), sq_dist(c, a) }) <= 0.0)
310 continue;
311 const double aspect2 = tri_aspect_sq(t);
312 if (aspect2 >= aspect_thr2)
313 round_aspect[t] = aspect2;
314 }
315 });
316 // Worst first; ties keep ascending triangle order so the pass is deterministic. Sorting the
317 // (thinness, triangle) pairs themselves gives exactly the order the index sort with its
318 // indirect comparator did, without that comparator's extra lookup on every comparison.
319 std::vector<std::pair<double, int>> cand;
320 for (size_t t = 0; t < tri_count; ++t)
321 if (round_aspect[t] >= 0.0)
322 cand.emplace_back(round_aspect[t], int(t));
323 std::sort(cand.begin(), cand.end(), [](const std::pair<double, int> &x, const std::pair<double, int> &y) {
324 return x.first != y.first ? x.first > y.first : x.second < y.second;
325 });
326
327 size_t round_collapses = 0;
328 for (const std::pair<double, int> &entry : cand) {
329 const size_t t = size_t(entry.second);
330 if (tri_deleted[t])
331 continue;
332 const int a = corners[t * 3], b = corners[t * 3 + 1], c = corners[t * 3 + 2];
333 // All three edges, shortest first: a sliver straddling a seam has its shortest edge
334 // crossing it, which the normal gate refuses, while a long edge along one surface
335 // collapses safely. Trying only the shortest would leave those stuck.
336 struct Cand { double len2; int u, v; };
337 Cand e[3] = { { sq_dist(a, b), a, b }, { sq_dist(b, c), b, c }, { sq_dist(c, a), c, a } };
338 std::stable_sort(std::begin(e), std::end(e),
339 [](const Cand &x, const Cand &y) { return x.len2 < y.len2; });
340 if (try_collapse(e[0].u, e[0].v) || try_collapse(e[1].u, e[1].v) ||
341 try_collapse(e[2].u, e[2].v))
342 ++round_collapses;
343 }
344 result.collapse_count += round_collapses;
345 if (round_collapses == 0)
346 break;
347 }
348
349 // Drop deleted triangles and rebuild the soup.
350 const bool have_weights = !geometry.exclude_weight.empty();
351 const size_t survivors = tri_count - size_t(std::count(tri_deleted.begin(), tri_deleted.end(), uint8_t(1)));
352 std::vector<int> out_parent;
353 TriSoup &out = result.geometry;
354 out_parent.reserve(survivors);
355 out.pos.reserve(survivors * 3);
356 if (have_weights)
357 out.exclude_weight.reserve(survivors * 3);
358 for (size_t t = 0; t < tri_count; ++t) {
359 if (tri_deleted[t])
360 continue;
361 for (int k = 0; k < 3; ++k)
362 out.pos.push_back(vert[size_t(corners[t * 3 + size_t(k)])].cast<float>());
363 if (have_weights) {
364 // Constant across a face's corners.
365 const float w = geometry.exclude_weight[t * 3];
366 out.exclude_weight.insert(out.exclude_weight.end(), { w, w, w });
367 }
368 out_parent.push_back(result.face_parent_id[t]);
369 }
370 result.face_parent_id = std::move(out_parent);
371
372 // Rebuilt from the compacted geometry - the collapses moved vertices.
373 out.nrm.assign(out.pos.size(), Vec3f::Zero());
374 {
375 QuantizedPointMap weld(WELD_GRID_GEOMETRY, out.pos.size());
376 std::vector<int> vid(out.pos.size());
377 int next = 0;
378 for (size_t i = 0; i < out.pos.size(); ++i) {
379 vid[i] = weld.get_or_set(out.pos[i], next);
380 if (weld.inserted())
381 ++next;
382 }
383 std::vector<Vec3d> vn(size_t(next), Vec3d::Zero());
384 for (size_t t = 0; t * 3 < out.pos.size(); ++t) {
385 const Vec3d a = out.pos[t * 3].cast<double>();
386 const Vec3d n = (out.pos[t * 3 + 1].cast<double>() - a).cross(out.pos[t * 3 + 2].cast<double>() - a);
387 for (int k = 0; k < 3; ++k)
388 vn[size_t(vid[t * 3 + size_t(k)])] += n;
389 }
390 for (size_t i = 0; i < out.pos.size(); ++i) {
391 const Vec3d &n = vn[size_t(vid[i])];
392 const double l = n.norm();
393 out.nrm[i] = (l > 0.0) ? Vec3d(n / l).cast<float>() : Vec3f(0.f, 0.f, 1.f);
394 }
395 }
396 return result;
397}
TriSoup geometry
Definition TextureBakeRegularize.hpp:64
bool preserve_excluded
Definition TextureBakeRegularize.hpp:52
size_t collapse_count
Definition TextureBakeRegularize.hpp:66
double extreme_sliver_aspect
Definition TextureBakeRegularize.hpp:37
double aggressive_slack
Definition TextureBakeRegularize.hpp:34
RegularizeRejectStats reject_stats
Definition TextureBakeRegularize.hpp:67
double sharp_edge_cos
Definition TextureBakeRegularize.hpp:47
double aspect_threshold
Definition TextureBakeRegularize.hpp:28
std::vector< int > face_parent_id
Definition TextureBakeRegularize.hpp:65
double max_normal_delta_cos
Definition TextureBakeRegularize.hpp:43
int maxrounds
Definition TextureBakeRegularize.hpp:49
double aggressive_normal_delta_cos
Definition TextureBakeRegularize.hpp:44
double slack
Definition TextureBakeRegularize.hpp:33
Definition TextureBakeRegularize.hpp:57
Definition TextureBakeRegularize.hpp:63
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
T dot(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:154
size_t triangle_count() const
Definition TextureBakeIndex.hpp:147

References Slic3r::TextureBake::RegularizeOptions::aggressive_normal_delta_cos, Slic3r::TextureBake::RegularizeOptions::aggressive_slack, Slic3r::TextureBake::RegularizeOptions::aspect_threshold, Slic3r::TextureBake::RegularizeResult::collapse_count, Slic3r::cross(), Slic3r::cross2(), Slic3r::TextureBake::RegularizeRejectStats::degenerate, Slic3r::dot(), Slic3r::TextureBake::RegularizeRejectStats::edge_cap, Slic3r::TextureBake::TriSoup::exclude_weight, Slic3r::TextureBake::RegularizeOptions::extreme_sliver_aspect, Slic3r::TextureBake::RegularizeResult::face_parent_id, Slic3r::TextureBake::RegularizeRejectStats::folded_apex, Slic3r::TextureBake::RegularizeRejectStats::frozen, Slic3r::TextureBake::RegularizeResult::geometry, Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::QuantizedPointMap::get_or_set_key(), Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::RegularizeRejectStats::link_condition, Slic3r::TextureBake::RegularizeOptions::max_normal_delta_cos, Slic3r::TextureBake::RegularizeOptions::maxrounds, Slic3r::TextureBake::RegularizeRejectStats::normal_change, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::TriSoup::pos, Slic3r::TextureBake::RegularizeOptions::preserve_excluded, Slic3r::TextureBake::RegularizeResult::reject_stats, Slic3r::TextureBake::RegularizeOptions::sharp_edge_cos, Slic3r::TextureBake::RegularizeOptions::slack, Slic3r::TextureBake::TriSoup::triangle_count(), WELD_GRID_GEOMETRY, and Slic3r::TextureBake::RegularizeRejectStats::wing_count.

Referenced by run_pipeline().

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

◆ relocate_to_contours()

RelocateResult Slic3r::TextureBake::relocate_to_contours ( const TriSoup & geometry,
const HeightSampleFn & sample,
const RelocateSettings & settings,
const std::vector< uint8_t > & locked )
34{
35 RelocateResult result;
36 result.geometry = geometry;
37 const size_t count = geometry.pos.size();
38 const size_t tri_ct = count / 3;
39 if (count == 0 || !sample || settings.iterations <= 0)
40 return result;
41
42 // Weld, so every copy of a position moves together and the mesh cannot come apart.
43 QuantizedPointMap weld(WELD_GRID_GEOMETRY, std::min(count, size_t(1) << 22));
44 std::vector<int> vid(count);
45 std::vector<Vec3f> pos;
46 for (size_t i = 0; i < count; ++i) {
47 vid[i] = weld.get_or_set(geometry.pos[i], int(pos.size()));
48 if (weld.inserted())
49 pos.push_back(geometry.pos[i]);
50 }
51 const size_t nv = pos.size();
52
53 // Incident corners per position, CSR style, plus the mean incident edge length that sets the scale
54 // for both the finite difference and the move limit.
55 std::vector<uint32_t> start(nv + 1, 0);
56 for (size_t i = 0; i < count; ++i)
57 ++start[size_t(vid[i]) + 1];
58 for (size_t v = 0; v < nv; ++v)
59 start[v + 1] += start[v];
60 std::vector<uint32_t> inc(count), cursor(nv, 0);
61 for (size_t i = 0; i < count; ++i)
62 inc[start[size_t(vid[i])] + cursor[size_t(vid[i])]++] = uint32_t(i);
63
64 std::vector<uint8_t> frozen(nv, 0);
65 if (!locked.empty())
66 for (size_t t = 0; t < tri_ct && t < locked.size(); ++t)
67 if (locked[t])
68 for (int k = 0; k < 3; ++k)
69 frozen[size_t(vid[t * 3 + size_t(k)])] = 1;
70
71 std::vector<float> edge_len(nv, 0.f), normal_len(nv, 0.f);
72 std::vector<Vec3f> nrm(nv, Vec3f::Zero());
73 const auto rebuild_frames = [&]() {
74 std::fill(nrm.begin(), nrm.end(), Vec3f::Zero());
75 std::fill(edge_len.begin(), edge_len.end(), 0.f);
76 std::vector<uint32_t> deg(nv, 0);
77 for (size_t t = 0; t < tri_ct; ++t) {
78 const int a = vid[t * 3], b = vid[t * 3 + 1], c = vid[t * 3 + 2];
79 const Vec3f fn = (pos[size_t(b)] - pos[size_t(a)]).cross(pos[size_t(c)] - pos[size_t(a)]);
80 for (int k = 0; k < 3; ++k) {
81 const int u = vid[t * 3 + size_t(k)], w = vid[t * 3 + size_t((k + 1) % 3)];
82 nrm[size_t(u)] += fn;
83 edge_len[size_t(u)] += (pos[size_t(w)] - pos[size_t(u)]).norm();
84 ++deg[size_t(u)];
85 }
86 }
87 for (size_t v = 0; v < nv; ++v) {
88 const float l = nrm[v].norm();
89 nrm[v] = (l > 0.f) ? Vec3f(nrm[v] / l) : Vec3f(0.f, 0.f, 1.f);
90 edge_len[v] = deg[v] > 0 ? edge_len[v] / float(deg[v]) : 0.f;
91 }
92 };
93 rebuild_frames();
94
95 // The level to snap onto, taken from the height actually present on this patch rather than assumed.
96 // A texture that never reaches full black or white would otherwise be measured against a range it
97 // does not occupy.
98 double h_lo = std::numeric_limits<double>::max(), h_hi = -h_lo;
99 {
100 std::vector<float> h0(nv, 0.f);
101 tbb::parallel_for(tbb::blocked_range<size_t>(0, nv), [&](const tbb::blocked_range<size_t> &r) {
102 for (size_t v = r.begin(); v < r.end(); ++v)
103 h0[v] = sample(pos[v], nrm[v], nrm[v]);
104 });
105 for (const float h : h0) {
106 h_lo = std::min(h_lo, double(h));
107 h_hi = std::max(h_hi, double(h));
108 }
109 }
110 const double h_range = h_hi - h_lo;
111 if (!(h_range > 0.0))
112 return result; // a flat height field has no contour to snap to
113 const double target = h_lo + h_range * settings.contour_level;
114 // A gradient is worth acting on when the height changes by this much across one edge length.
115 const double min_grad = h_range * settings.min_gradient_fraction;
116
117 std::vector<uint8_t> ever_moved(nv, 0);
118
119 for (int iter = 0; iter < settings.iterations; ++iter) {
120 std::vector<Vec3f> proposal(nv);
121 std::vector<uint8_t> want(nv, 0);
122
123 tbb::parallel_for(tbb::blocked_range<size_t>(0, nv), [&](const tbb::blocked_range<size_t> &r) {
124 for (size_t v = r.begin(); v < r.end(); ++v) {
125 if (frozen[v] || edge_len[v] <= 0.f)
126 continue;
127 const Vec3f n = nrm[v];
128 Vec3f t1, t2;
129 tangent_basis(n, t1, t2);
130 const float eps = edge_len[v] * float(settings.gradient_step_fraction);
131 if (eps <= 0.f)
132 continue;
133
134 // Central differences in the tangent plane. Sampling the field itself, not the mesh,
135 // so the gradient is the image's, at whatever resolution the mesh happens to have.
136 const double h = double(sample(pos[v], n, n));
137 const double gx = (double(sample(pos[v] + t1 * eps, n, n)) -
138 double(sample(pos[v] - t1 * eps, n, n))) / (2.0 * double(eps));
139 const double gy = (double(sample(pos[v] + t2 * eps, n, n)) -
140 double(sample(pos[v] - t2 * eps, n, n))) / (2.0 * double(eps));
141 const double g2 = gx * gx + gy * gy;
142 if (g2 <= 0.0)
143 continue;
144 // Scale-free test: how much the height changes across one edge, versus the patch range.
145 if (std::sqrt(g2) * double(edge_len[v]) < min_grad)
146 continue;
147
148 // Newton step onto the level set h = target, expressed back in 3D.
149 const double s = -(h - target) / g2;
150 Vec3f d = t1 * float(s * gx) + t2 * float(s * gy);
151 const float cap = edge_len[v] * float(settings.max_move_fraction);
152 const float len = d.norm();
153 if (len <= 0.f)
154 continue;
155 if (len > cap)
156 d *= cap / len;
157 proposal[v] = pos[v] + d;
158 want[v] = 1;
159 }
160 });
161
162 // Apply one at a time: a move is only valid against the neighbourhood as it stands, and two
163 // adjacent vertices moving together can invert a triangle neither would have on its own.
164 size_t applied = 0;
165 for (size_t v = 0; v < nv; ++v) {
166 if (!want[v])
167 continue;
168 const Vec3f old = pos[v];
169 pos[v] = proposal[v];
170 bool ok = true;
171 for (uint32_t k = start[v]; k < start[v + 1] && ok; ++k) {
172 const size_t t = size_t(inc[k]) / 3;
173 const Vec3f &a = pos[size_t(vid[t * 3])];
174 const Vec3f n2 = (pos[size_t(vid[t * 3 + 1])] - a).cross(pos[size_t(vid[t * 3 + 2])] - a);
175 // Compared against the frame this vertex carried before the move: a triangle that
176 // flips or collapses means the move crossed a neighbour.
177 if (n2.squaredNorm() <= 0.f || n2.normalized().dot(nrm[v]) < 0.f)
178 ok = false;
179 }
180 if (ok) {
181 ++applied;
182 ever_moved[v] = 1;
183 } else {
184 pos[v] = old;
185 ++result.rejected;
186 }
187 }
188 if (applied == 0)
189 break;
190 rebuild_frames();
191 }
192
193 for (size_t v = 0; v < nv; ++v)
194 if (ever_moved[v])
195 ++result.moved;
196
197 // Write the relocated positions back to every copy, and rebuild the per-face normals.
198 for (size_t i = 0; i < count; ++i)
199 result.geometry.pos[i] = pos[size_t(vid[i])];
200 for (size_t t = 0; t < tri_ct; ++t) {
201 Vec3f n = (result.geometry.pos[t * 3 + 1] - result.geometry.pos[t * 3])
202 .cross(result.geometry.pos[t * 3 + 2] - result.geometry.pos[t * 3]);
203 const float len = n.norm();
204 n = (len > 0.f) ? Vec3f(n / len) : Vec3f(0.f, 0.f, 1.f);
205 result.geometry.nrm[t * 3] = result.geometry.nrm[t * 3 + 1] = result.geometry.nrm[t * 3 + 2] = n;
206 }
207 return result;
208}
void tangent_basis(const Vec3f &n, Vec3f &t1, Vec3f &t2)
Definition TextureBakeRelocate.cpp:23
int iterations
Definition TextureBakeRelocate.hpp:39
double contour_level
Definition TextureBakeRelocate.hpp:53
double gradient_step_fraction
Definition TextureBakeRelocate.hpp:56
size_t rejected
Definition TextureBakeRelocate.hpp:63
size_t moved
Definition TextureBakeRelocate.hpp:62
double min_gradient_fraction
Definition TextureBakeRelocate.hpp:49
double max_move_fraction
Definition TextureBakeRelocate.hpp:44
TriSoup geometry
Definition TextureBakeRelocate.hpp:61
Definition TextureBakeRelocate.hpp:60

References Slic3r::TextureBake::RelocateSettings::contour_level, count, Slic3r::cross(), Slic3r::TextureBake::RelocateResult::geometry, Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::RelocateSettings::gradient_step_fraction, Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::RelocateSettings::iterations, Slic3r::TextureBake::RelocateSettings::max_move_fraction, Slic3r::TextureBake::RelocateSettings::min_gradient_fraction, Slic3r::TextureBake::RelocateResult::moved, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::TriSoup::pos, Slic3r::TextureBake::RelocateResult::rejected, and WELD_GRID_GEOMETRY.

Referenced by run_pipeline().

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

◆ resolve_t_junctions()

TriSoup Slic3r::TextureBake::resolve_t_junctions ( const TriSoup & geometry,
const RepairOptions & opts,
std::vector< int > * face_color )
77{
78 const size_t n_tri = geometry.triangle_count();
79 const double on_tol2 = opts.on_seg_tol * opts.on_seg_tol;
80 const double Q = opts.weld_quant;
81
82 // Snapped, not just welded: keeping unrounded coordinates lets a thin triangle pass the
83 // degeneracy test here and then collapse to collinear once the file is written, punching the very
84 // hole this pass prevents. Snapping makes the check see what will be written.
85 QuantizedPointMap vmap(Q, std::min(n_tri * 3, size_t(1) << 22));
86 std::vector<Vec3d> vert;
87 std::vector<int> vid(n_tri * 3);
88 for (size_t i = 0; i < n_tri * 3; ++i) {
89 const Vec3f &p = geometry.pos[i];
90 const int id = vmap.get_or_set(p, int(vert.size()));
91 if (vmap.inserted())
92 vert.emplace_back(double(grid_round(double(p.x()) * Q)) / Q,
93 double(grid_round(double(p.y()) * Q)) / Q,
94 double(grid_round(double(p.z()) * Q)) / Q);
95 vid[i] = id;
96 }
97
98 // Dropped: faces whose corners welded together, and needles - distinct but collinear on this
99 // grid. A needle reads as watertight yet is deleted downstream, and dropping it leaves exactly
100 // the on-edge-vertex topology the pass below closes.
101 std::vector<std::array<int, 3>> faces;
102 // Parallel to `faces` throughout, so a split or a dropped degenerate keeps the two in step.
103 const bool track_color = face_color != nullptr && !face_color->empty();
104 std::vector<int> colors;
105 faces.reserve(n_tri);
106 if (track_color)
107 colors.reserve(n_tri);
108 for (size_t t = 0; t < n_tri; ++t) {
109 const int a = vid[t * 3], b = vid[t * 3 + 1], c = vid[t * 3 + 2];
110 if (a == b || b == c || a == c)
111 continue;
112 const Vec3d u = vert[size_t(b)] - vert[size_t(a)];
113 const Vec3d w = vert[size_t(c)] - vert[size_t(a)];
114 if (u.cross(w).squaredNorm() < DEGENERATE_AREA_SQ)
115 continue;
116 faces.push_back({ a, b, c });
117 if (track_color)
118 colors.push_back(t < face_color->size() ? (*face_color)[t] : -1);
119 }
120
121 for (int iter = 0; iter < opts.max_iters; ++iter) {
122 std::unordered_map<uint64_t, int> e_count;
123 for (const auto &f : faces)
124 for (int e = 0; e < 3; ++e)
125 ++e_count[edge_key(f[size_t(e)], f[size_t((e + 1) % 3)])];
126
127 std::unordered_set<int> bverts;
128 for (const auto &[key, c] : e_count) {
129 if (c != 1)
130 continue;
131 bverts.insert(int(uint32_t(key >> 32)));
132 bverts.insert(int(uint32_t(key & 0xFFFFFFFFu)));
133 }
134 if (bverts.empty())
135 break;
136 const std::vector<int> bv(bverts.begin(), bverts.end());
137
138 struct Split { int a, b; std::vector<int> mids; };
139 std::unordered_map<size_t, Split> splits;
140 for (size_t fi = 0; fi < faces.size(); ++fi) {
141 const auto &f = faces[fi];
142 for (int e = 0; e < 3; ++e) {
143 const int a = f[size_t(e)], b = f[size_t((e + 1) % 3)];
144 if (e_count[edge_key(a, b)] != 1)
145 continue; // only a boundary edge carries an unresolved T-junction
146 const Vec3d A = vert[size_t(a)];
147 const Vec3d ev = vert[size_t(b)] - A;
148 const double elen2 = ev.squaredNorm();
149 if (elen2 < 1e-20)
150 continue;
151 std::vector<std::pair<double, int>> found;
152 for (const int c : bv) {
153 if (c == a || c == b)
154 continue;
155 const Vec3d cv = vert[size_t(c)] - A;
156 const double tp = cv.dot(ev) / elen2;
157 if (tp <= 1e-4 || tp >= 1.0 - 1e-4)
158 continue; // strictly between the ends
159 if ((cv - ev * tp).squaredNorm() < on_tol2)
160 found.emplace_back(tp, c);
161 }
162 if (!found.empty()) {
163 std::sort(found.begin(), found.end(),
164 [](const auto &x, const auto &y) { return x.first < y.first; });
165 Split sp{ a, b, {} };
166 for (const auto &m : found)
167 sp.mids.push_back(m.second);
168 splits.emplace(fi, std::move(sp));
169 break; // one site per face per pass; iteration handles cascades
170 }
171 }
172 }
173 if (splits.empty())
174 break;
175
176 std::vector<std::array<int, 3>> next;
177 std::vector<int> next_colors;
178 next.reserve(faces.size() + splits.size() * 2);
179 if (track_color)
180 next_colors.reserve(next.capacity());
181 for (size_t fi = 0; fi < faces.size(); ++fi) {
182 const auto it = splits.find(fi);
183 if (it == splits.end()) {
184 next.push_back(faces[fi]);
185 if (track_color)
186 next_colors.push_back(colors[fi]);
187 continue;
188 }
189 const auto &f = faces[fi];
190 const auto &sp = it->second;
191 const int apex = (f[0] != sp.a && f[0] != sp.b) ? f[0]
192 : (f[1] != sp.a && f[1] != sp.b) ? f[1]
193 : f[2];
194 // Walk the base the way the face already traverses it, so the winding survives.
195 bool dir_ab = false;
196 for (int e = 0; e < 3; ++e)
197 if (f[size_t(e)] == sp.a && f[size_t((e + 1) % 3)] == sp.b) {
198 dir_ab = true;
199 break;
200 }
201 std::vector<int> seq;
202 if (dir_ab) {
203 seq.push_back(sp.a);
204 seq.insert(seq.end(), sp.mids.begin(), sp.mids.end());
205 seq.push_back(sp.b);
206 } else {
207 seq.push_back(sp.b);
208 seq.insert(seq.end(), sp.mids.rbegin(), sp.mids.rend());
209 seq.push_back(sp.a);
210 }
211 for (size_t s = 0; s + 1 < seq.size(); ++s) {
212 next.push_back({ seq[s], seq[s + 1], apex });
213 if (track_color)
214 next_colors.push_back(colors[fi]); // every piece of a split face keeps its colour
215 }
216 }
217 faces.swap(next);
218 if (track_color)
219 colors.swap(next_colors);
220 }
221 if (track_color)
222 *face_color = std::move(colors);
223
224 TriSoup out;
225 out.pos.reserve(faces.size() * 3);
226 out.nrm.reserve(faces.size() * 3);
227 for (const auto &f : faces) {
228 const Vec3f a = vert[size_t(f[0])].cast<float>();
229 const Vec3f b = vert[size_t(f[1])].cast<float>();
230 const Vec3f c = vert[size_t(f[2])].cast<float>();
231 Vec3f nrm = (b - a).cross(c - a);
232 const float len = nrm.norm();
233 nrm = (len > 0.f) ? Vec3f(nrm / len) : Vec3f(0.f, 0.f, 1.f);
234 out.pos.insert(out.pos.end(), { a, b, c });
235 out.nrm.insert(out.nrm.end(), { nrm, nrm, nrm });
236 }
237 return out;
238}
double weld_quant
Definition TextureBakeRepair.hpp:35
int max_iters
Definition TextureBakeRepair.hpp:43
double on_seg_tol
Definition TextureBakeRepair.hpp:40
int64_t grid_round(double v)
Definition TextureBakeIndex.hpp:30
uint64_t edge_key(int a, int b)
Definition texture_unwrap_dump.cpp:37

References Slic3r::cross(), Slic3r::f(), Slic3r::TextureBake::QuantizedPointMap::get_or_set(), grid_round(), Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::RepairOptions::max_iters, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::RepairOptions::on_seg_tol, Slic3r::TextureBake::TriSoup::pos, Slic3r::TextureBake::TriSoup::triangle_count(), and Slic3r::TextureBake::RepairOptions::weld_quant.

Referenced by run_pipeline().

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

◆ run_pipeline()

PipelineResult Slic3r::TextureBake::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 )
134{
135 PipelineResult result;
136 const auto report = [&](const char *stage, double f) {
137 return !on_progress || on_progress(stage, f);
138 };
139 // Per-stage wall time. The stages differ in cost by orders of magnitude depending on the model, so
140 // without this it is guesswork which one to attack.
141 auto clock_now = [] { return std::chrono::steady_clock::now(); };
142 auto t_stage = clock_now();
143 // One call site for both the log line and the debug capture, so a stage cannot appear in one and
144 // be missing from the other. The capture happens after the elapsed time is read: welding the soup
145 // and scanning its edges costs more than some of the stages do, and must not land inside the
146 // measurement it is reporting.
147 const auto lap = [&](const char *stage, const TriSoup &geometry, const std::string &detail = {}) {
148 const double ms = std::chrono::duration<double, std::milli>(clock_now() - t_stage).count();
149 BOOST_LOG_TRIVIAL(info) << "TextureBake " << stage << ": " << ms << " ms, "
150 << geometry.triangle_count() << " tris";
151 if (debug != nullptr)
152 debug->capture(stage, geometry, ms, detail);
153 t_stage = clock_now();
154 };
155
156 if (input.empty() || !sample) {
157 result.geometry = input;
158 return result;
159 }
160 if (debug != nullptr)
161 debug->capture("input", input, 0.0, "as handed to the pipeline");
162 t_stage = clock_now(); // the capture above is not part of the first stage
163
164 // 1. Refine to the target edge length.
166 input, settings.refine_length, face_excluded, /* fast */ false, settings.safety_cap,
167 [&](double f, size_t, double) { return report("subdivide", f); }, settings.paint_within);
168 result.safety_cap_hit = sub.safety_cap_hit;
169 lap("subdivide", sub.geometry);
170 if (!report("subdivide", 1.0)) {
171 result.canceled = true;
172 return result;
173 }
174
175 // 2. Dissolve the slivers refinement inherited, then recover the edges that lengthened.
176 if (settings.regularize) {
177 RegularizeOptions ropts = settings.regularize_opts;
178 ropts.preserve_excluded = settings.preserve_untextured;
179 RegularizeResult reg = regularize_mesh(sub.geometry, sub.face_parent_id,
180 settings.refine_length, ropts);
181 result.collapse_count = reg.collapse_count;
182 lap("regularize", reg.geometry, std::to_string(reg.collapse_count) + " collapses");
183 if (!report("regularize", 1.0)) {
184 result.canceled = true;
185 return result;
186 }
187 if (reg.collapse_count > 0) {
188 // Excluded faces are carried on the soup itself, so the flag is re-derived rather than
189 // indexed across the collapse.
190 std::vector<uint8_t> excl;
191 if (!reg.geometry.exclude_weight.empty()) {
192 excl.assign(reg.geometry.triangle_count(), 0);
193 for (size_t t = 0; t < excl.size(); ++t)
194 excl[t] = reg.geometry.exclude_weight[t * 3] > 0.99f ? 1 : 0;
195 }
196 sub = subdivide(reg.geometry, settings.refine_length * settings.regularize_second_pass_mul,
197 excl, false, settings.safety_cap,
198 [&](double f, size_t, double) { return report("re-subdivide", f); },
199 settings.paint_within);
200 result.safety_cap_hit = result.safety_cap_hit || sub.safety_cap_hit;
201 // The second pass renumbers faces, so the parent map has to be composed through it.
202 std::vector<int> composed(sub.face_parent_id.size());
203 for (size_t i = 0; i < composed.size(); ++i) {
204 const int mid = sub.face_parent_id[i];
205 composed[i] = (mid >= 0 && size_t(mid) < reg.face_parent_id.size())
206 ? reg.face_parent_id[size_t(mid)] : -1;
207 }
208 sub.face_parent_id = std::move(composed);
209 lap("re-subdivide", sub.geometry);
210 } else {
211 sub.geometry = std::move(reg.geometry);
212 sub.face_parent_id = std::move(reg.face_parent_id);
213 }
214 }
215
216 // 2b. Paint finer than the input triangles. The caller includes a source triangle when any part of
217 // it is painted; now that the faces are small, ask once more per face and switch the unpainted
218 // ones off. They are pinned like the excluded region from here on: their own corners at weight 1,
219 // and the displacement's boundary sealing pins the stroke's rim on the painted side.
220 if (settings.painted) {
221 const size_t nf = sub.geometry.triangle_count();
222 const bool have_w = !sub.geometry.exclude_weight.empty();
223 std::vector<uint8_t> unpainted(nf, 0);
224 tbb::parallel_for(tbb::blocked_range<size_t>(0, nf), [&](const tbb::blocked_range<size_t> &r) {
225 for (size_t t = r.begin(); t < r.end(); ++t) {
226 if (have_w && sub.geometry.exclude_weight[t * 3] > 0.99f)
227 continue; // excluded from the start, never asked
228 const Vec3f &a = sub.geometry.pos[t * 3], &b = sub.geometry.pos[t * 3 + 1], &c = sub.geometry.pos[t * 3 + 2];
229 if (!settings.painted((a + b + c) / 3.f))
230 unpainted[t] = 1;
231 }
232 });
233 size_t switched = 0;
234 for (size_t t = 0; t < nf; ++t)
235 switched += unpainted[t];
236 if (switched > 0) {
237 if (sub.geometry.exclude_weight.empty())
238 sub.geometry.exclude_weight.assign(sub.geometry.pos.size(), 0.f);
239 for (size_t t = 0; t < nf; ++t)
240 if (unpainted[t])
241 sub.geometry.exclude_weight[t * 3] = sub.geometry.exclude_weight[t * 3 + 1] =
242 sub.geometry.exclude_weight[t * 3 + 2] = 1.f;
243 }
244 lap("paint", sub.geometry, std::to_string(switched) + " faces switched off");
245 if (!report("paint", 1.0)) {
246 result.canceled = true;
247 return result;
248 }
249 }
250
251 // 3. Align the mesh to the height field's edges, then displace.
252 if (settings.relocate) {
253 std::vector<uint8_t> locked;
254 if (settings.preserve_untextured && !sub.geometry.exclude_weight.empty()) {
255 locked.assign(sub.geometry.triangle_count(), 0);
256 for (size_t t = 0; t < locked.size(); ++t)
257 locked[t] = sub.geometry.exclude_weight[t * 3] > 0.99f ? 1 : 0;
258 }
259 RelocateResult rel = relocate_to_contours(sub.geometry, sample, settings.relocate_opts, locked);
260 BOOST_LOG_TRIVIAL(info) << "TextureBake relocate: moved=" << rel.moved
261 << " rejected=" << rel.rejected;
262 sub.geometry = std::move(rel.geometry);
263 lap("relocate", sub.geometry,
264 "moved " + std::to_string(rel.moved) + ", rejected " + std::to_string(rel.rejected));
265 }
266
267 // 3b. Diagonals along the height field's steps, so they displace into straight walls.
268 if (settings.flip_edges) {
269 std::vector<uint8_t> locked;
270 if (settings.preserve_untextured && !sub.geometry.exclude_weight.empty()) {
271 locked.assign(sub.geometry.triangle_count(), 0);
272 for (size_t t = 0; t < locked.size(); ++t)
273 locked[t] = sub.geometry.exclude_weight[t * 3] > 0.99f ? 1 : 0;
274 }
275 FlipResult fl = flip_edges_to_height(sub.geometry, sub.face_parent_id, sample, settings.flip_opts, locked);
276 sub.geometry = std::move(fl.geometry);
277 sub.face_parent_id = std::move(fl.face_parent_id);
278 lap("align edges", sub.geometry, std::to_string(fl.flipped) + " flips");
279 if (!report("align edges", 1.0)) {
280 result.canceled = true;
281 return result;
282 }
283 }
284
285 TriSoup displaced = apply_displacement(sub.geometry, sample, settings.displace, bounds,
286 [&](double f) { return report("displace", f); });
287 lap("displace", displaced);
288 if (!report("displace", 1.0)) {
289 result.canceled = true;
290 return result;
291 }
292
293 // Colour per face, taken here and carried from here on. This is the only point where the paint mask
294 // is exact: `exclude_weight` says which faces the paint left out, and the mesh is still the refined
295 // one the displacement produced. Everything downstream (the collapse, the T-junction repair) carries
296 // these along rather than sampling again, and the caller uses them as they are.
297 //
298 // It also gives the collapse its crease criterion: an edge between two colours is never collapsed
299 // across, which is what keeps a survivor's colour well defined.
300 if (color_sample) {
301 const size_t nf = displaced.triangle_count();
302 result.face_color.assign(nf, -1);
303 const bool have_w = !displaced.exclude_weight.empty();
304 tbb::parallel_for(tbb::blocked_range<size_t>(0, nf), [&](const tbb::blocked_range<size_t> &r) {
305 for (size_t t = r.begin(); t < r.end(); ++t) {
306 // Unpainted faces take no colour at all, which is what stops the texture appearing on
307 // surfaces the paint never covered.
308 if (have_w && (displaced.exclude_weight[t * 3] + displaced.exclude_weight[t * 3 + 1] +
309 displaced.exclude_weight[t * 3 + 2]) / 3.f > 0.99f)
310 continue; // stays FACE_UNPAINTED
311 const Vec3f &a = displaced.pos[t * 3], &b = displaced.pos[t * 3 + 1], &c = displaced.pos[t * 3 + 2];
312 const int sampled = color_sample((a + b + c) / 3.f, displaced.nrm[t * 3]);
313 // Painted either way. The sampler expects a point on the base surface and these are on
314 // the displaced one, so off the patch by more than its tolerance it simply says "no
315 // colour" - which must not be confused with "not painted".
316 result.face_color[t] = (sampled >= 0) ? sampled : FACE_NO_COLOUR;
317 }
318 });
319 }
320
321 // 4. Decimate - export only. A bake needs the face-parent map, which a collapse destroys.
322 std::vector<int> parent = std::move(sub.face_parent_id);
323 const size_t displaced_before_decimate = displaced.triangle_count();
324 if (mode == PipelineMode::Export) {
325 std::vector<uint8_t> locked;
326 size_t preserved = 0;
327 {
328 if (settings.preserve_untextured && !displaced.exclude_weight.empty()) {
329 locked.assign(displaced.triangle_count(), 0);
330 // The corner average, as the displacement stage judges it: after the flip stage's
331 // per-vertex merge an included face touching the excluded region carries one corner
332 // at weight 1, and must stay free to collapse and to take colour.
333 for (size_t t = 0; t < locked.size(); ++t)
334 locked[t] = (displaced.exclude_weight[t * 3] + displaced.exclude_weight[t * 3 + 1] +
335 displaced.exclude_weight[t * 3 + 2]) / 3.f > 0.99f ? 1 : 0;
336 // That includes the faces the paint test switched off: the harvest would re-triangulate
337 // them into long slivers, which a later bake painted there would refine instead of the
338 // grid the graded refinement left.
339 preserved = size_t(std::count(locked.begin(), locked.end(), uint8_t(1)));
340 }
341 }
342 // The budget is what this bake may spend on what it refines. Geometry it only preserves - the
343 // unpainted surface, and on it the relief of an earlier bake - is counted on top of it: charged
344 // against the same budget, a second bake over a fresh area had to evict the first one's
345 // triangles to fit, so every bake after the first came out coarser than the one before.
346 const size_t target = settings.max_triangles + preserved;
347 const bool over_budget = displaced.triangle_count() > target;
348 // Flat faces are harvested whether or not the budget bites. Refinement is driven by the target
349 // edge length alone, so it leaves as fine a mesh over the flat parts of a texture as over its
350 // detail, and nothing else removes those: under its budget a bake kept every redundant triangle
351 // unless the budget was lowered until decimation had to run. Only collapses costing less than
352 // harvest_tol are taken, so this does not reach the relief.
353 const bool harvest_only = !over_budget && settings.harvest_flat && displaced.triangle_count() > 0;
354 std::vector<int> &face_color = result.face_color;
355 if (over_budget || harvest_only) {
356 // Harvesting alone is asked for by handing it the count it already has: nothing is then
357 // over the target, so the loop only ever pops collapses under the tolerance.
358 const size_t before = displaced.triangle_count();
359 DecimateResult dec = decimate(displaced, over_budget ? target : before, settings.harvest_flat,
360 settings.harvest_tol, locked,
361 [&](double f) { return report("decimate", f); }, face_color);
362 result.locked_over_budget = dec.locked_over_budget;
363 result.budget_limited = result.simplified = dec.target_cost_detail;
364 displaced = std::move(dec.geometry);
365 face_color = std::move(dec.face_color);
366 lap("decimate", displaced, over_budget ? "over budget, simplified" : "flat faces harvested");
367 BOOST_LOG_TRIVIAL(info) << "TextureBake decimate: " << before << " -> " << displaced.triangle_count()
368 << (over_budget ? " (budget " : " (flat harvest, budget ") << target << ")";
369 parent.clear(); // no longer meaningful
370 }
371 result.triangles_refined = displaced_before_decimate;
372 result.triangles_budget = target;
373 if (!report("decimate", 1.0)) {
374 result.canceled = true;
375 return result;
376 }
377 }
378
379 // 5. Flatten the bed-contact surface.
380 {
381 const bool clamped = settings.clamp_below_plate || settings.displace.bottom_angle_limit > 0.f;
382 if (clamped)
383 clamp_below_bottom(displaced, bounds.min.z());
384 size_t snapped = 0;
385 if (settings.bottom_snap_tol > 0.0)
386 snapped = snap_bottom_to_flat(displaced, bounds.min.z(), settings.bottom_snap_tol);
387 if (clamped || settings.bottom_snap_tol > 0.0)
388 lap("bottom clamp + snap", displaced, std::to_string(snapped) + " triangles snapped flat");
389 }
390
391 // 6. Close the T-junctions decimation left behind. Only meaningful when it ran.
392 if (mode == PipelineMode::Export && parent.empty()) {
393 displaced = resolve_t_junctions(displaced, {}, &result.face_color);
394 lap("repair", displaced);
395 }
396
397 result.geometry = std::move(displaced);
398 result.face_parent_id = std::move(parent);
399 return result;
400}
void capture(const char *name, const TextureBake::TriSoup &soup, double ms, const std::string &detail={})
Definition TextureBakeDebug.cpp:82
bool budget_limited
Definition TextureBakePipeline.hpp:144
bool relocate
Definition TextureBakePipeline.hpp:60
TriSoup geometry
Definition TextureBakePipeline.hpp:117
RelocateSettings relocate_opts
Definition TextureBakePipeline.hpp:61
size_t max_triangles
Definition TextureBakePipeline.hpp:87
std::vector< int > face_color
Definition TextureBakePipeline.hpp:131
size_t triangles_budget
Definition TextureBakePipeline.hpp:143
bool locked_over_budget
Definition TextureBakePipeline.hpp:135
size_t triangles_refined
Definition TextureBakePipeline.hpp:142
double harvest_tol
Definition TextureBakePipeline.hpp:91
DisplaceSettings displace
Definition TextureBakePipeline.hpp:68
bool flip_edges
Definition TextureBakePipeline.hpp:65
DecimateResult decimate(const TriSoup &geometry, size_t target_triangles, bool harvest_flat, double harvest_tol, const std::vector< uint8_t > &locked_faces, const DecimateProgressFn &on_progress, const std::vector< int > &face_color)
Definition TextureBakeDecimate.cpp:105
RegularizeResult regularize_mesh(const TriSoup &geometry, const std::vector< int > &face_parent_id, double max_edge_length, const RegularizeOptions &opts)
Definition TextureBakeRegularize.cpp:56
static constexpr int FACE_NO_COLOUR
Definition TextureBakePipeline.hpp:113
double refine_length
Definition TextureBakePipeline.hpp:49
RegularizeOptions regularize_opts
Definition TextureBakePipeline.hpp:53
size_t collapse_count
Definition TextureBakePipeline.hpp:136
FlipSettings flip_opts
Definition TextureBakePipeline.hpp:66
TriSoup apply_displacement(const TriSoup &geometry, const HeightSampleFn &sample, const DisplaceSettings &settings, const DisplaceBounds &bounds, const DisplaceProgressFn &on_progress)
Definition TextureBakeDisplace.cpp:20
FlipResult flip_edges_to_height(const TriSoup &geometry, const std::vector< int > &face_parent_id, const HeightSampleFn &sample, const FlipSettings &settings, const std::vector< uint8_t > &locked)
Definition TextureBakeFlip.cpp:22
bool simplified
Definition TextureBakePipeline.hpp:150
std::function< bool(const Vec3f &centroid)> painted
Definition TextureBakePipeline.hpp:75
bool preserve_untextured
Definition TextureBakePipeline.hpp:93
bool safety_cap_hit
Definition TextureBakePipeline.hpp:134
double regularize_second_pass_mul
Definition TextureBakePipeline.hpp:56
SubdivideWithinFn paint_within
Definition TextureBakePipeline.hpp:81
size_t snap_bottom_to_flat(TriSoup &geometry, float bottom_z, double tol)
Definition TextureBakePipeline.cpp:51
void clamp_below_bottom(TriSoup &geometry, float bottom_z)
Definition TextureBakePipeline.cpp:33
TriSoup resolve_t_junctions(const TriSoup &geometry, const RepairOptions &opts, std::vector< int > *face_color)
Definition TextureBakeRepair.cpp:75
bool canceled
Definition TextureBakePipeline.hpp:137
double bottom_snap_tol
Definition TextureBakePipeline.hpp:100
bool harvest_flat
Definition TextureBakePipeline.hpp:90
std::vector< int > face_parent_id
Definition TextureBakePipeline.hpp:133
int safety_cap
Definition TextureBakePipeline.hpp:102
SubdivideResult subdivide(const TriSoup &geometry, double max_edge_length, const std::vector< uint8_t > &face_excluded, bool fast, int safety_cap, const SubdivideProgressFn &on_progress, const SubdivideWithinFn &within)
Definition TextureBakeSubdivide.cpp:426
RelocateResult relocate_to_contours(const TriSoup &geometry, const HeightSampleFn &sample, const RelocateSettings &settings, const std::vector< uint8_t > &locked)
Definition TextureBakeRelocate.cpp:32
bool regularize
Definition TextureBakePipeline.hpp:52
bool clamp_below_plate
Definition TextureBakePipeline.hpp:97
Definition TextureBakeRegularize.hpp:25
Definition TextureBakeSubdivide.hpp:70
Definition TextureBakePipeline.hpp:116
bool empty() const
Definition TextureBakeIndex.hpp:148

References apply_displacement(), Slic3r::TextureBake::DisplaceSettings::bottom_angle_limit, Slic3r::TextureBake::PipelineSettings::bottom_snap_tol, Slic3r::bounds(), Slic3r::TextureBake::PipelineResult::budget_limited, Slic3r::TextureBake::PipelineResult::canceled, Slic3r::BakeStageRecorder::capture(), clamp_below_bottom(), Slic3r::TextureBake::PipelineSettings::clamp_below_plate, Slic3r::TextureBake::PipelineResult::collapse_count, Slic3r::TextureBake::RegularizeResult::collapse_count, decimate(), Slic3r::TextureBake::PipelineSettings::displace, Slic3r::TextureBake::TriSoup::empty(), Slic3r::TextureBake::TriSoup::exclude_weight, Export, Slic3r::f(), Slic3r::TextureBake::DecimateResult::face_color, Slic3r::TextureBake::PipelineResult::face_color, FACE_NO_COLOUR, Slic3r::TextureBake::FlipResult::face_parent_id, Slic3r::TextureBake::PipelineResult::face_parent_id, Slic3r::TextureBake::RegularizeResult::face_parent_id, Slic3r::TextureBake::SubdivideResult::face_parent_id, Slic3r::TextureBake::PipelineSettings::flip_edges, flip_edges_to_height(), Slic3r::TextureBake::PipelineSettings::flip_opts, Slic3r::TextureBake::FlipResult::flipped, Slic3r::TextureBake::DecimateResult::geometry, Slic3r::TextureBake::FlipResult::geometry, Slic3r::TextureBake::PipelineResult::geometry, Slic3r::TextureBake::RegularizeResult::geometry, Slic3r::TextureBake::RelocateResult::geometry, Slic3r::TextureBake::SubdivideResult::geometry, Slic3r::TextureBake::PipelineSettings::harvest_flat, Slic3r::TextureBake::PipelineSettings::harvest_tol, Slic3r::TextureBake::DecimateResult::locked_over_budget, Slic3r::TextureBake::PipelineResult::locked_over_budget, Slic3r::TextureBake::PipelineSettings::max_triangles, Slic3r::TextureBake::RelocateResult::moved, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::PipelineSettings::paint_within, Slic3r::TextureBake::PipelineSettings::painted, Slic3r::TextureBake::TriSoup::pos, Slic3r::TextureBake::RegularizeOptions::preserve_excluded, Slic3r::TextureBake::PipelineSettings::preserve_untextured, Slic3r::TextureBake::PipelineSettings::refine_length, Slic3r::TextureBake::PipelineSettings::regularize, regularize_mesh(), Slic3r::TextureBake::PipelineSettings::regularize_opts, Slic3r::TextureBake::PipelineSettings::regularize_second_pass_mul, Slic3r::TextureBake::RelocateResult::rejected, Slic3r::TextureBake::PipelineSettings::relocate, Slic3r::TextureBake::PipelineSettings::relocate_opts, relocate_to_contours(), resolve_t_junctions(), Slic3r::TextureBake::PipelineSettings::safety_cap, Slic3r::TextureBake::PipelineResult::safety_cap_hit, Slic3r::TextureBake::SubdivideResult::safety_cap_hit, Slic3r::TextureBake::PipelineResult::simplified, snap_bottom_to_flat(), subdivide(), Slic3r::TextureBake::DecimateResult::target_cost_detail, Slic3r::TextureBake::TriSoup::triangle_count(), Slic3r::TextureBake::PipelineResult::triangles_budget, and Slic3r::TextureBake::PipelineResult::triangles_refined.

Referenced by Slic3r::anonymous_namespace{TextureDisplacement.cpp}::build_texture_displacement_v2().

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

◆ snap_bottom_to_flat()

size_t Slic3r::TextureBake::snap_bottom_to_flat ( TriSoup & geometry,
float bottom_z,
double tol )
52{
53 const size_t vert_count = geometry.pos.size();
54 const size_t tri_count = vert_count / 3;
55 if (tri_count == 0 || tol <= 0.0)
56 return 0;
57
58 // Weld at the finest grid: by this point copies of one position are bit-identical, because every
59 // earlier stage moved them by the same vector.
60 QuantizedPointMap weld(WELD_GRID_DECIMATION, std::min(vert_count, size_t(1) << 22));
61 std::vector<int> vid(vert_count);
62 int unique = 0;
63 for (size_t i = 0; i < vert_count; ++i) {
64 vid[i] = weld.get_or_set(geometry.pos[i], unique);
65 if (weld.inserted())
66 ++unique;
67 }
68 // Incident corners per position, CSR style.
69 std::vector<uint32_t> start(size_t(unique) + 1, 0);
70 for (size_t i = 0; i < vert_count; ++i)
71 ++start[size_t(vid[i]) + 1];
72 for (size_t id = 0; id < size_t(unique); ++id)
73 start[id + 1] += start[id];
74 std::vector<uint32_t> inc(vert_count), cursor(size_t(unique), 0);
75 for (size_t i = 0; i < vert_count; ++i)
76 inc[start[size_t(vid[i])] + cursor[size_t(vid[i])]++] = uint32_t(i);
77
78 const double fold_cos = std::cos(75.0 * M_PI / 180.0);
79 std::vector<uint8_t> dirty_tri(tri_count, 0);
80
81 for (size_t id = 0; id < size_t(unique); ++id) {
82 const float z = geometry.pos[inc[start[id]]].z();
83 if (z == bottom_z || std::abs(double(z) - double(bottom_z)) > tol)
84 continue;
85
86 // Simulate the move: every incident triangle must keep positive area and must not fold.
87 bool ok = true;
88 for (uint32_t k = start[id]; k < start[id + 1] && ok; ++k) {
89 const size_t t = size_t(inc[k]) / 3;
90 Vec3f p[3];
91 for (int v = 0; v < 3; ++v) {
92 p[v] = geometry.pos[t * 3 + size_t(v)];
93 if (vid[t * 3 + size_t(v)] == int(id))
94 p[v].z() = bottom_z;
95 }
96 const Vec3d on = (geometry.pos[t * 3 + 1] - geometry.pos[t * 3])
97 .cross(geometry.pos[t * 3 + 2] - geometry.pos[t * 3]).cast<double>();
98 const Vec3d nn = (p[1] - p[0]).cross(p[2] - p[0]).cast<double>();
99 const double o2 = on.squaredNorm(), n2 = nn.squaredNorm();
100 if (n2 < 1e-20) { ok = false; break; } // would collapse to zero area
101 if (o2 < 1e-20) continue; // already degenerate, cannot judge a rotation
102 const double dot = on.dot(nn);
103 if (dot < 0.0 || dot * dot < fold_cos * fold_cos * o2 * n2)
104 ok = false;
105 }
106 if (!ok)
107 continue;
108
109 for (uint32_t k = start[id]; k < start[id + 1]; ++k) {
110 geometry.pos[inc[k]].z() = bottom_z;
111 dirty_tri[size_t(inc[k]) / 3] = 1;
112 }
113 }
114
115 size_t dirty = 0;
116 for (size_t t = 0; t < tri_count; ++t) {
117 if (!dirty_tri[t])
118 continue;
119 ++dirty;
120 Vec3f n = (geometry.pos[t * 3 + 1] - geometry.pos[t * 3])
121 .cross(geometry.pos[t * 3 + 2] - geometry.pos[t * 3]);
122 const float len = n.norm();
123 n = (len > 0.f) ? Vec3f(n / len) : Vec3f(0.f, 0.f, 1.f);
124 geometry.nrm[t * 3] = geometry.nrm[t * 3 + 1] = geometry.nrm[t * 3 + 2] = n;
125 }
126 return dirty;
127}

References Slic3r::cross(), Slic3r::dot(), Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::QuantizedPointMap::inserted(), M_PI, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::TriSoup::pos, and WELD_GRID_DECIMATION.

Referenced by run_pipeline().

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

◆ subdivide()

SubdivideResult Slic3r::TextureBake::subdivide ( const TriSoup & geometry,
double max_edge_length,
const std::vector< uint8_t > & face_excluded,
bool fast,
int safety_cap,
const SubdivideProgressFn & on_progress,
const SubdivideWithinFn & within )
429{
430 SubdivideResult result;
431 if (geometry.empty() || max_edge_length <= 0.0) {
432 result.geometry = geometry;
433 return result;
434 }
435
436 IndexedMesh indexed = fast ? to_indexed_fast(geometry) : to_indexed(geometry);
437 QuantizedPointMap *canon_map = indexed.has_canon ? &indexed.pos_canon_map : nullptr;
438
439 const size_t initial_tris = indexed.indices.size() / 3;
440 std::vector<int> current_indices = std::move(indexed.indices); // nothing reads it again
441 std::vector<uint8_t> current_excluded = face_excluded;
442 std::vector<int> current_parent(initial_tris);
443 for (size_t i = 0; i < initial_tris; ++i)
444 current_parent[i] = int(i);
445
446 for (int iter = 0; iter < SUBDIVIDE_MAX_ITERATIONS; ++iter) {
447 if (current_indices.size() / 3 >= size_t(safety_cap)) {
448 result.safety_cap_hit = true;
449 break;
450 }
451
452 PassResult pass = subdivide_pass(indexed.verts, current_indices, max_edge_length, safety_cap,
453 current_excluded, canon_map, current_parent, within);
454 current_indices = std::move(pass.indices);
455 if (!pass.face_excluded.empty())
456 current_excluded = std::move(pass.face_excluded);
457 if (!pass.face_parent_id.empty())
458 current_parent = std::move(pass.face_parent_id);
459 if (pass.capped || current_indices.size() / 3 >= size_t(safety_cap))
460 result.safety_cap_hit = true;
461
462 if (on_progress) {
463 // Reported after the pass, so the value falls each iteration instead of lagging a step.
464 // A max is order-independent, so the scan reduces in parallel to the same value.
465 const double max_edge_sq = tbb::parallel_reduce(
466 tbb::blocked_range<size_t>(0, current_indices.size() / 3), 0.0,
467 [&](const tbb::blocked_range<size_t> &r, double acc) {
468 for (size_t f = r.begin(); f < r.end(); ++f) {
469 const int a = current_indices[f * 3], b = current_indices[f * 3 + 1],
470 c = current_indices[f * 3 + 2];
471 acc = std::max({ acc, edge_len_sq(indexed.verts, a, b), edge_len_sq(indexed.verts, b, c),
472 edge_len_sq(indexed.verts, c, a) });
473 }
474 return acc;
475 },
476 [](double x, double y) { return std::max(x, y); });
477 if (!on_progress(std::min(0.95, double(iter + 1) / SUBDIVIDE_MAX_ITERATIONS),
478 current_indices.size() / 3, std::sqrt(max_edge_sq)))
479 break; // whole passes only, so what we have is still crack-free
480 }
481
482 if (!pass.changed || result.safety_cap_hit)
483 break;
484 }
485
486 result.geometry = to_non_indexed(indexed.verts, current_indices, current_excluded);
487 result.face_parent_id = std::move(current_parent);
488 return result;
489}
TriSoup to_non_indexed(const VertStore &verts, const std::vector< int > &indices, const std::vector< uint8_t > &face_excluded)
Definition TextureBakeSubdivide.cpp:395
bool has_canon
Definition TextureBakeSubdivide.hpp:59
std::vector< int > face_parent_id
Definition TextureBakeSubdivide.hpp:73
IndexedMesh to_indexed(const TriSoup &geometry)
Definition TextureBakeSubdivide.cpp:312
static constexpr int SUBDIVIDE_MAX_ITERATIONS
Definition TextureBakeSubdivide.hpp:30
IndexedMesh to_indexed_fast(const TriSoup &geometry)
Definition TextureBakeSubdivide.cpp:283
TriSoup geometry
Definition TextureBakeSubdivide.hpp:71
bool safety_cap_hit
Definition TextureBakeSubdivide.hpp:74

References Slic3r::TextureBake::TriSoup::empty(), Slic3r::TextureBake::SubdivideResult::geometry, Slic3r::TextureBake::IndexedMesh::has_canon, Slic3r::TextureBake::IndexedMesh::indices, Slic3r::TextureBake::IndexedMesh::pos_canon_map, Slic3r::TextureBake::SubdivideResult::safety_cap_hit, SUBDIVIDE_MAX_ITERATIONS, to_indexed(), to_indexed_fast(), and Slic3r::TextureBake::IndexedMesh::verts.

Referenced by run_pipeline().

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

◆ to_indexed()

IndexedMesh Slic3r::TextureBake::to_indexed ( const TriSoup & geometry)
313{
314 // Export path. Two vertices at one position merge only when their face normals agree to within
315 // SUBDIVIDE_SHARP_ANGLE_DEG, which keeps a cylinder from faceting while stopping a cube's edge
316 // normal from leaking into the flat face interiors as subdivision carries it inward.
317 IndexedMesh out;
318 out.has_canon = true;
319 const size_t n = geometry.pos.size();
320 const bool has_w = !geometry.exclude_weight.empty();
321 const double sharp_cos = std::cos(SUBDIVIDE_SHARP_ANGLE_DEG * M_PI / 180.0);
322
323 // Per-face normals: unit for the angle test, raw for the area-weighted accumulation.
324 std::vector<Vec3d> face_unit(n), face_raw(n);
325 tbb::parallel_for(tbb::blocked_range<size_t>(0, n / 3), [&](const tbb::blocked_range<size_t> &range) {
326 for (size_t f = range.begin(); f < range.end(); ++f) {
327 const size_t t = f * 3;
328 const Vec3d a = geometry.pos[t].cast<double>();
329 const Vec3d r = (geometry.pos[t + 1].cast<double>() - a).cross(
330 geometry.pos[t + 2].cast<double>() - a);
331 const double len = r.norm();
332 const Vec3d u = (len > 0.0) ? Vec3d(r / len) : Vec3d(0.0, 0.0, 1.0);
333 for (int v = 0; v < 3; ++v) {
334 face_unit[t + size_t(v)] = u;
335 face_raw[t + size_t(v)] = r;
336 }
337 }
338 });
339
340 out.indices.resize(n);
341 out.pos_canon_map = QuantizedPointMap(WELD_GRID_GEOMETRY, std::min(n, size_t(1) << 22));
342 struct Cluster { int idx; Vec3d fn_unit; };
343 std::unordered_map<int, std::vector<Cluster>> clusters_by_canon;
344
345 for (size_t i = 0; i < n; ++i) {
346 const Vec3f &p = geometry.pos[i];
347 // The first vertex at a position becomes its canonical id; later split copies share it.
348 const int canon_id = out.pos_canon_map.get_or_set(p, int(out.verts.count()));
349 const bool fresh_position = out.pos_canon_map.inserted();
350
351 const auto add_vertex = [&](int canon) {
352 const int idx = out.verts.push(p.cast<double>(), face_raw[i]);
353 if (has_w)
354 out.verts.wgt.push_back(double(geometry.exclude_weight[i]));
355 out.verts.canon.push_back(canon);
356 return idx;
357 };
358
359 if (fresh_position) {
360 const int idx = add_vertex(canon_id);
361 clusters_by_canon[canon_id].push_back({ idx, face_unit[i] });
362 out.indices[i] = idx;
363 continue;
364 }
365
366 std::vector<Cluster> &clusters = clusters_by_canon[canon_id];
367 bool matched = false;
368 for (Cluster &cl : clusters) {
369 if (cl.fn_unit.dot(face_unit[i]) < sharp_cos)
370 continue;
371 out.verts.nrm[size_t(cl.idx)] += face_raw[i];
372 if (has_w && double(geometry.exclude_weight[i]) > out.verts.wgt[size_t(cl.idx)])
373 out.verts.wgt[size_t(cl.idx)] = double(geometry.exclude_weight[i]);
374 // Track the running average, so gradual curvature stays in one cluster instead of
375 // fragmenting when a distant face exceeds the threshold against the seed's fixed normal.
376 cl.fn_unit += face_unit[i];
377 if (const double rl = cl.fn_unit.norm(); rl > 0.0)
378 cl.fn_unit /= rl;
379 out.indices[i] = cl.idx;
380 matched = true;
381 break;
382 }
383 if (!matched) {
384 // A sharp-edge split: a new vertex at the same position, sharing its canonical id.
385 const int idx = add_vertex(canon_id);
386 clusters.push_back({ idx, face_unit[i] });
387 out.indices[i] = idx;
388 }
389 }
390
392 return out;
393}
bool inserted() const
Definition TextureBakeIndex.hpp:52
int get_or_set(float x, float y, float z, int value)
Definition TextureBakeIndex.hpp:63
void normalize_store_normals(VertStore &verts)
Definition TextureBakeSubdivide.cpp:30
static constexpr double SUBDIVIDE_SHARP_ANGLE_DEG
Definition TextureBakeSubdivide.hpp:27
QuantizedPointMap pos_canon_map
Definition TextureBakeSubdivide.hpp:58
std::vector< int > indices
Definition TextureBakeSubdivide.hpp:57
VertStore verts
Definition TextureBakeSubdivide.hpp:56
Definition TextureBakeSubdivide.hpp:55
void add_vertex(const Vec3 &position, const Vec3 &normal, std::vector< float > &vertices)
Definition Utils.cpp:15
size_t count() const
Definition TextureBakeSubdivide.hpp:44
std::vector< Vec3d > nrm
Definition TextureBakeSubdivide.hpp:40
std::vector< double > wgt
Definition TextureBakeSubdivide.hpp:41
int push(const Vec3d &p, const Vec3d &n)
Definition TextureBakeSubdivide.hpp:45
std::vector< int > canon
Definition TextureBakeSubdivide.hpp:42

References Slic3r::TextureBake::VertStore::canon, Slic3r::TextureBake::VertStore::count(), Slic3r::cross(), Slic3r::TextureBake::TriSoup::exclude_weight, Slic3r::f(), Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::IndexedMesh::has_canon, Slic3r::TextureBake::IndexedMesh::indices, Slic3r::TextureBake::QuantizedPointMap::inserted(), M_PI, Slic3r::TextureBake::VertStore::nrm, Slic3r::TextureBake::TriSoup::pos, Slic3r::TextureBake::IndexedMesh::pos_canon_map, Slic3r::TextureBake::VertStore::push(), Slic3r::range(), SUBDIVIDE_SHARP_ANGLE_DEG, Slic3r::TextureBake::IndexedMesh::verts, WELD_GRID_GEOMETRY, and Slic3r::TextureBake::VertStore::wgt.

Referenced by subdivide().

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

◆ to_indexed_fast()

IndexedMesh Slic3r::TextureBake::to_indexed_fast ( const TriSoup & geometry)
284{
285 // Preview path: a plain position merge - no clustering, no sharp-edge splitting, no canonical ids.
286 IndexedMesh out;
287 const size_t n = geometry.pos.size();
288 QuantizedPointMap vert_map(WELD_GRID_GEOMETRY, std::min(n, size_t(1) << 22));
289 out.indices.resize(n);
290 const bool has_w = !geometry.exclude_weight.empty();
291
292 for (size_t i = 0; i < n; ++i) {
293 const Vec3f &p = geometry.pos[i];
294 const Vec3f nf = geometry.nrm.empty() ? Vec3f(0.f, 0.f, 1.f) : geometry.nrm[i];
295 const int idx = vert_map.get_or_set(p, int(out.verts.count()));
296 if (vert_map.inserted()) {
297 out.verts.push(p.cast<double>(), nf.cast<double>());
298 if (has_w)
299 out.verts.wgt.push_back(double(geometry.exclude_weight[i]));
300 } else {
301 out.verts.nrm[size_t(idx)] += nf.cast<double>();
302 // Merge exclusion by maximum: any excluded face marks the shared vertex.
303 if (has_w && double(geometry.exclude_weight[i]) > out.verts.wgt[size_t(idx)])
304 out.verts.wgt[size_t(idx)] = double(geometry.exclude_weight[i]);
305 }
306 out.indices[i] = idx;
307 }
309 return out;
310}

References Slic3r::TextureBake::VertStore::count(), Slic3r::TextureBake::TriSoup::exclude_weight, Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::IndexedMesh::indices, Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::VertStore::nrm, Slic3r::TextureBake::TriSoup::pos, Slic3r::TextureBake::VertStore::push(), Slic3r::TextureBake::IndexedMesh::verts, WELD_GRID_GEOMETRY, and Slic3r::TextureBake::VertStore::wgt.

Referenced by subdivide().

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

◆ to_indexed_triangle_set()

indexed_triangle_set Slic3r::TextureBake::to_indexed_triangle_set ( const TriSoup & soup,
std::vector< int > * face_color )
47{
48 indexed_triangle_set out;
49 const size_t n = soup.pos.size();
50 out.indices.reserve(n / 3);
51 QuantizedPointMap map(WELD_GRID_GEOMETRY, std::min(n, size_t(1) << 22));
52 std::vector<int> id(n);
53 for (size_t i = 0; i < n; ++i) {
54 id[i] = map.get_or_set(soup.pos[i], int(out.vertices.size()));
55 if (map.inserted())
56 out.vertices.push_back(soup.pos[i]);
57 }
58 const bool track_color = face_color != nullptr && !face_color->empty();
59 std::vector<int> kept_color;
60 if (track_color)
61 kept_color.reserve(face_color->size());
62 for (size_t t = 0; t + 2 < n; t += 3) {
63 // Welded-together corners carry no area.
64 if (id[t] == id[t + 1] || id[t + 1] == id[t + 2] || id[t] == id[t + 2])
65 continue;
66 out.indices.emplace_back(id[t], id[t + 1], id[t + 2]);
67 if (track_color) {
68 const size_t src = t / 3;
69 kept_color.push_back(src < face_color->size() ? (*face_color)[src] : -1);
70 }
71 }
72 if (track_color)
73 *face_color = std::move(kept_color);
74 return out;
75}

References Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::TriSoup::pos, and WELD_GRID_GEOMETRY.

Referenced by Slic3r::anonymous_namespace{TextureDisplacement.cpp}::build_texture_displacement_v2().

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

◆ to_non_indexed()

TriSoup Slic3r::TextureBake::to_non_indexed ( const VertStore & verts,
const std::vector< int > & indices,
const std::vector< uint8_t > & face_excluded )
397{
398 TriSoup out;
399 const size_t tri_count = indices.size() / 3;
400 out.pos.resize(tri_count * 3);
401 out.nrm.resize(tri_count * 3);
402 const bool want_weights = !face_excluded.empty() || !verts.wgt.empty();
403 if (want_weights)
404 out.exclude_weight.resize(tri_count * 3);
405
406 // Each triangle writes only its own three slots, so this is a plain parallel gather.
407 tbb::parallel_for(tbb::blocked_range<size_t>(0, tri_count), [&](const tbb::blocked_range<size_t> &r) {
408 for (size_t t = r.begin(); t < r.end(); ++t) {
409 // The per-face flag, not the interpolated weight: merging by maximum can push an *included*
410 // face's corners to 1 when it borders two excluded neighbours, wrongly excluding it.
411 const bool have_face_flag = !face_excluded.empty();
412 const float face_w = have_face_flag ? (face_excluded[t] ? 1.f : 0.f) : 0.f;
413 for (int v = 0; v < 3; ++v) {
414 const size_t vidx = size_t(indices[t * 3 + size_t(v)]);
415 out.pos[t * 3 + size_t(v)] = verts.pos[vidx].cast<float>();
416 out.nrm[t * 3 + size_t(v)] = verts.nrm[vidx].cast<float>();
417 if (want_weights)
418 out.exclude_weight[t * 3 + size_t(v)] =
419 have_face_flag ? face_w : float(verts.wgt[vidx]);
420 }
421 }
422 });
423 return out;
424}
std::vector< Vec3d > pos
Definition TextureBakeSubdivide.hpp:39

References Slic3r::TextureBake::TriSoup::exclude_weight, Slic3r::TextureBake::TriSoup::nrm, Slic3r::TextureBake::VertStore::nrm, Slic3r::TextureBake::TriSoup::pos, Slic3r::TextureBake::VertStore::pos, and Slic3r::TextureBake::VertStore::wgt.

◆ to_soup()

TriSoup Slic3r::TextureBake::to_soup ( const indexed_triangle_set & its,
const std::vector< uint8_t > & face_excluded )
15{
16 TriSoup out;
17 const size_t n = its.indices.size();
18 out.pos.resize(n * 3);
19 out.nrm.resize(n * 3);
20 const bool have_excl = face_excluded.size() == n;
21 if (have_excl)
22 out.exclude_weight.resize(n * 3);
23
24 for (size_t t = 0; t < n; ++t) {
25 const stl_triangle_vertex_indices &tri = its.indices[t];
26 const Vec3f a = its.vertices[size_t(tri[0])];
27 const Vec3f b = its.vertices[size_t(tri[1])];
28 const Vec3f c = its.vertices[size_t(tri[2])];
29 Vec3f nrm = (b - a).cross(c - a);
30 const float len = nrm.norm();
31 nrm = (len > 0.f) ? Vec3f(nrm / len) : Vec3f(0.f, 0.f, 1.f);
32 out.pos[t * 3] = a;
33 out.pos[t * 3 + 1] = b;
34 out.pos[t * 3 + 2] = c;
35 // Per-face on purpose: the accurate indexer derives smooth normals and splits at sharp edges
36 // itself, so averaged ones would pre-empt that.
37 out.nrm[t * 3] = out.nrm[t * 3 + 1] = out.nrm[t * 3 + 2] = nrm;
38 if (have_excl) {
39 const float w = face_excluded[t] ? 1.f : 0.f;
40 out.exclude_weight[t * 3] = out.exclude_weight[t * 3 + 1] = out.exclude_weight[t * 3 + 2] = w;
41 }
42 }
43 return out;
44}

References Slic3r::cross(), Slic3r::TextureBake::TriSoup::exclude_weight, Slic3r::TextureBake::TriSoup::nrm, and Slic3r::TextureBake::TriSoup::pos.

Referenced by Slic3r::anonymous_namespace{TextureDisplacement.cpp}::build_texture_displacement_v2().

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

◆ weld_vertices()

WeldResult Slic3r::TextureBake::weld_vertices ( const std::vector< Vec3f > & positions,
double quant )
12{
13 WeldResult out;
14 QuantizedPointMap map(quant, std::min<size_t>(positions.size(), size_t(1) << 22));
15 out.vertex_id.resize(positions.size());
16 int next_id = 0;
17 for (size_t i = 0; i < positions.size(); ++i) {
18 const int id = map.get_or_set(positions[i], next_id);
19 if (map.inserted())
20 ++next_id;
21 out.vertex_id[i] = id;
22 }
23 out.unique_count = next_id;
24 return out;
25}
int unique_count
Definition TextureBakeIndex.hpp:155
std::vector< int > vertex_id
Definition TextureBakeIndex.hpp:154
Definition TextureBakeIndex.hpp:153

References Slic3r::TextureBake::QuantizedPointMap::get_or_set(), Slic3r::TextureBake::QuantizedPointMap::inserted(), Slic3r::TextureBake::WeldResult::unique_count, and Slic3r::TextureBake::WeldResult::vertex_id.

Here is the call graph for this function:

Variable Documentation

◆ DECIMATE_CREASE_COS

double Slic3r::TextureBake::DECIMATE_CREASE_COS = 0.5
staticconstexpr

Referenced by decimate().

◆ DECIMATE_CREASE_WEIGHT

double Slic3r::TextureBake::DECIMATE_CREASE_WEIGHT = 1e4
staticconstexpr

Referenced by decimate().

◆ DECIMATE_DEFAULT_HARVEST_TOL

double Slic3r::TextureBake::DECIMATE_DEFAULT_HARVEST_TOL = 0.005
staticconstexpr

◆ DECIMATE_FLIP_DOT

double Slic3r::TextureBake::DECIMATE_FLIP_DOT = 0.2
staticconstexpr

Referenced by decimate().

◆ DECIMATE_MAX_VALENCE

int Slic3r::TextureBake::DECIMATE_MAX_VALENCE = 64
staticconstexpr

Referenced by decimate().

◆ FACE_NO_COLOUR

int Slic3r::TextureBake::FACE_NO_COLOUR = -2
staticconstexpr

Referenced by run_pipeline().

◆ FACE_UNPAINTED

int Slic3r::TextureBake::FACE_UNPAINTED = -1
staticconstexpr

◆ SUBDIVIDE_GRADE

double Slic3r::TextureBake::SUBDIVIDE_GRADE = 1.0
staticconstexpr

◆ SUBDIVIDE_MAX_ITERATIONS

int Slic3r::TextureBake::SUBDIVIDE_MAX_ITERATIONS = 12
staticconstexpr

Referenced by subdivide().

◆ SUBDIVIDE_SAFETY_CAP

int Slic3r::TextureBake::SUBDIVIDE_SAFETY_CAP = 16'000'000
staticconstexpr

◆ SUBDIVIDE_SHARP_ANGLE_DEG

double Slic3r::TextureBake::SUBDIVIDE_SHARP_ANGLE_DEG = 30.0
staticconstexpr

Referenced by to_indexed().

◆ WELD_GRID_DECIMATION

double Slic3r::TextureBake::WELD_GRID_DECIMATION = 1e6
staticconstexpr

Referenced by decimate(), and snap_bottom_to_flat().

◆ WELD_GRID_EXPORT

double Slic3r::TextureBake::WELD_GRID_EXPORT = 1e4
staticconstexpr

◆ WELD_GRID_GEOMETRY