Files
OrcaSlicer/src/libslic3r/TextureBake/TextureBakePipeline.hpp
T
ExPikaPaka 2c8e1f1c24 Merge branch 'main' into feature/texdisp-color-mixing
The "Mix by" combo main reworded is gone on this branch: ColorMixMode went with
the per-triangle interleave. Kept its removal, with main's spelling for the line
that stays, and updated the shader comments main added, which still described
the interleave.
2026-10-07 09:08:13 +02:00

176 lines
9.1 KiB
C++

#pragma once
// The bake pipeline:
//
// subdivide -> [regularize -> re-subdivide] -> [paint test] -> [relocate] -> [flip edges]
// -> displace -> [decimate] -> bottom clamp -> bottom snap -> [resolve T-junctions]
//
// Regularization sits between two subdivisions on purpose: it dissolves the slivers refinement
// inherited, which lengthens some edges past the target, and the second pass brings those back.
// Before any subdivision it would have nothing to work on, since the slivers come from refining a
// needle; after a single pass it would leave the mesh coarser than asked for.
//
// Decimation and repair are export-only - decimation drops the output-to-input face mapping a bake
// needs to carry per-face data forward.
#include <cstddef>
#include <cstdint>
#include <functional>
#include <vector>
#include "libslic3r/Point.hpp"
#include "TextureBakeDecimate.hpp"
#include "TextureBakeDisplace.hpp"
#include "TextureBakeIndex.hpp"
#include "TextureBakeRegularize.hpp"
#include "TextureBakeFlip.hpp"
#include "TextureBakeRelocate.hpp"
#include "TextureBakeSubdivide.hpp"
namespace Slic3r {
// Optional step-by-step capture; see TextureBakeDebug.hpp. A pointer, and forward declared, so the
// pipeline header stays free of the mesh types the recorder converts into.
class BakeStageRecorder;
namespace TextureBake {
enum class PipelineMode
{
// Keeps the face-parent mapping; skips decimation and repair.
Bake,
// The full sequence, including decimation and repair.
Export,
};
struct PipelineSettings
{
// Target edge length for the refinement, in mm.
double refine_length = 1.0;
// Sliver removal between the two subdivision passes.
bool regularize = true;
RegularizeOptions regularize_opts;
// Slightly above the first pass, so it recovers the edges regularization lengthened instead of
// re-refining what it just merged.
double regularize_second_pass_mul = 1.1;
// Slide vertices onto the height map's own edges before displacing, so a step lands on a mesh
// edge instead of being quantised to wherever the grid fell.
bool relocate = false;
RelocateSettings relocate_opts;
// Choose each quad's diagonal to follow the height field before displacing, so a step that crosses
// the grid at an angle comes out as a straight wall instead of a sawtooth. See TextureBakeFlip.hpp.
bool flip_edges = true;
FlipSettings flip_opts;
DisplaceSettings displace;
// Optional. Asked once per refined face (its centroid, in the soup's coordinates) after the
// refinement stages and before displacement, for faces whose source triangle was included:
// false marks the face as unpainted (no displacement), so paint finer than the input triangles
// is honoured. Faces excluded from the start are never asked. Called from several threads at
// once, so it must be safe to call concurrently.
std::function<bool(const Vec3f &centroid)> painted;
// Optional. Whether the paint comes within a radius of a point, which grades the refinement (see
// subdivide()): only the paint and its surroundings reach refine_length, and the unpainted surface
// further out stays in whole pieces of its own grid. That is what a later bake painted there
// refines, so it meets the same grid a first bake does. Called from several threads at once.
SubdivideWithinFn paint_within;
// Export mode only.
// What this bake may spend on what it refines. Geometry it only preserves (see preserve_untextured),
// the unpainted surface and an earlier bake's relief on it, is counted on top of it, so that relief
// does not have to be evicted to fit this one.
size_t max_triangles = 750'000;
// Keep removing zero-cost flat faces past the target. Only applies when decimation runs, i.e. when
// the displaced mesh is over the budget - an under-budget mesh is never decimated.
bool harvest_flat = true;
double harvest_tol = DECIMATE_DEFAULT_HARVEST_TOL;
// Lock the untextured region against both regularization and decimation.
bool preserve_untextured = true;
// Push anything that displaced below the plate back up to it. Downward movement is otherwise left
// alone, so relief on the underside is kept - only what would sink through the plate is stopped.
bool clamp_below_plate = false;
// Snap vertices within this of the bottom plane onto it. 0 disables.
double bottom_snap_tol = 0.1;
int safety_cap = SUBDIVIDE_SAFETY_CAP;
};
// Stage name and a fraction within it. Returning false cancels the run.
using PipelineProgressFn = std::function<bool(const char *stage, double fraction)>;
// Colour class of a point of the surface (a palette index, -1 for none), for the decimation's
// colour-boundary creases. Only consulted when the mesh is over budget.
using ColorSampleFn = std::function<int(const Vec3f &centroid, const Vec3f &normal)>;
// Sentinels for PipelineResult::face_color.
static constexpr int FACE_UNPAINTED = -1; // the paint did not cover this face's origin
static constexpr int FACE_NO_COLOUR = -2; // painted, but the sampler returned nothing at this point
struct PipelineResult
{
TriSoup geometry;
// One entry per output face, carried through decimation, the T-junction repair and the weld.
// FACE_UNPAINTED means the paint never covered the geometry this face came from; anything else means
// it did, and is the palette index `color_sample` returned there (FACE_NO_COLOUR when it returned
// none). The distinction matters: the sampler answers for points on the *base* surface, and these
// are sampled on the displaced one, so a painted face can easily come back without a colour. Only
// the painted/unpainted split is reliable here, and that is what a caller should use it for.
//
// Empty unless the caller gave a `color_sample`.
//
// A caller that needs per-face colour must use this rather than sampling the result again. The
// result is displaced geometry: a point on it is no longer where its base surface was, so matching
// it back by proximity colours whatever base surface happens to be nearest - which on a part thinner
// than the relief depth is the *opposite* face, picking up the texture meant for the painted one.
std::vector<int> face_color;
// Output face -> input face. Empty in Export mode, where decimation invalidates it.
std::vector<int> face_parent_id;
bool safety_cap_hit = false;
bool locked_over_budget = false;
size_t collapse_count = 0;
bool canceled = false;
// What the refinement produced, before decimation, and the count it had to fit into (the budget
// plus the preserved geometry). budget_limited says the refined mesh did not fit without collapsing
// more than flat faces: the result carries less of the texture than the resolution asked for,
// which is what a caller warns about.
size_t triangles_refined = 0;
size_t triangles_budget = 0;
bool budget_limited = false;
// The budget coarsened the mesh, so its triangles are no longer the fine, even ones the refinement
// laid down - they can be as large as the features on them, which a caller that post-processes per
// triangle (the colour despeckle) has to know. Harvesting flat faces does not count: what it
// removes carried nothing. Not the same question as "did a collapse run", which is why
// face_parent_id being empty is no longer used to answer it.
bool simplified = false;
};
// `debug`, when given and enabled, receives the mesh after every stage that ran - which is the only
// way to tell which stage a bad result came from, since each one rewrites the whole mesh.
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 = nullptr, const ColorSampleFn &color_sample = {});
// Snap anything that ended below the model's original bottom back up to it.
void clamp_below_bottom(TriSoup &geometry, float bottom_z);
// Flatten the bed-contact surface by snapping positions within `tol` of the bottom plane onto it.
//
// Gated, not unconditional: an unconditional band snap also flattens the undersides of texture relief
// near the base, folding them coplanar into the bottom face. Folded faces overlap the plate, so edges
// there pick up four incident faces - non-manifold edges and phantom shells on re-import. All copies
// of a position move together, and the move is rejected if any incident triangle would go degenerate
// or rotate more than about 75 degrees. A real bed-contact sliver rotates by a fraction of a degree
// and still snaps. Returns how many triangles moved.
size_t snap_bottom_to_flat(TriSoup &geometry, float bottom_z, double tol = 0.1);
} // namespace TextureBake
} // namespace Slic3r