diff --git a/src/libslic3r/TextureDisplacement.cpp b/src/libslic3r/TextureDisplacement.cpp index 4700ca952b..6038ac2ac3 100644 --- a/src/libslic3r/TextureDisplacement.cpp +++ b/src/libslic3r/TextureDisplacement.cpp @@ -1570,6 +1570,61 @@ std::vector compute_connected_net(const PatchUnwrap &unwrap) return islands; } +std::vector transfer_facet_paint(const indexed_triangle_set &src_mesh, + const TriangleSelector::TriangleSplittingData &src_paint, + const indexed_triangle_set &dst_mesh) +{ + if (src_mesh.indices.empty() || dst_mesh.indices.empty() || src_paint.bitstream.empty()) + return {}; + + const TriangleMesh src(src_mesh); + TriangleSelector selector(src); + selector.deserialize(src_paint, false); + + // Every sub-triangle of the source, each carrying its own state - NONE included, so the soup + // covers the whole surface and a foot point always lands on something. One soup rather than one + // tree per state: a foot point has one nearest triangle, and that triangle's state is the answer. + std::vector per_state; + selector.get_facets(per_state); + indexed_triangle_set soup; + std::vector label; + for (size_t st = 0; st < per_state.size(); ++st) { + const indexed_triangle_set &part = per_state[st]; + if (part.indices.empty()) + continue; + const int base = int(soup.vertices.size()); + soup.vertices.insert(soup.vertices.end(), part.vertices.begin(), part.vertices.end()); + for (const stl_triangle_vertex_indices &t : part.indices) { + soup.indices.emplace_back(t[0] + base, t[1] + base, t[2] + base); + label.push_back(uint8_t(st)); + } + } + if (soup.indices.empty()) + return {}; + + const AABBTreeIndirect::Tree3f tree = + AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(soup.vertices, soup.indices); + + std::vector out(dst_mesh.indices.size(), 0); + tbb::parallel_for(tbb::blocked_range(0, dst_mesh.indices.size()), + [&](const tbb::blocked_range &range) { + for (size_t j = range.begin(); j < range.end(); ++j) { + const stl_triangle_vertex_indices &t = dst_mesh.indices[j]; + // The centroid, not a corner: a facet takes one state, and its middle is the point that + // is inside it whatever the triangle looks like. + const Vec3f centroid = (dst_mesh.vertices[size_t(t[0])] + dst_mesh.vertices[size_t(t[1])] + + dst_mesh.vertices[size_t(t[2])]) / 3.f; + size_t hit = 0; + Vec3f foot; + const float d2 = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(soup.vertices, soup.indices, + tree, centroid, hit, foot); + if (d2 >= 0.f && hit < label.size()) + out[j] = label[hit]; + } + }); + return out; +} + void average_island_scales(std::vector &islands) { if (islands.empty()) @@ -1776,61 +1831,6 @@ std::vector transfer_uv_channel(const indexed_triangle_set &src_mesh, return out; } -std::vector transfer_facet_paint(const indexed_triangle_set &src_mesh, - const TriangleSelector::TriangleSplittingData &src_paint, - const indexed_triangle_set &dst_mesh) -{ - if (src_mesh.indices.empty() || dst_mesh.indices.empty() || src_paint.bitstream.empty()) - return {}; - - const TriangleMesh src(src_mesh); - TriangleSelector selector(src); - selector.deserialize(src_paint, false); - - // Every sub-triangle of the source, each carrying its own state - NONE included, so the soup - // covers the whole surface and a foot point always lands on something. One soup rather than one - // tree per state: a foot point has one nearest triangle, and that triangle's state is the answer. - std::vector per_state; - selector.get_facets(per_state); - indexed_triangle_set soup; - std::vector label; - for (size_t st = 0; st < per_state.size(); ++st) { - const indexed_triangle_set &part = per_state[st]; - if (part.indices.empty()) - continue; - const int base = int(soup.vertices.size()); - soup.vertices.insert(soup.vertices.end(), part.vertices.begin(), part.vertices.end()); - for (const stl_triangle_vertex_indices &t : part.indices) { - soup.indices.emplace_back(t[0] + base, t[1] + base, t[2] + base); - label.push_back(uint8_t(st)); - } - } - if (soup.indices.empty()) - return {}; - - const AABBTreeIndirect::Tree3f tree = - AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(soup.vertices, soup.indices); - - std::vector out(dst_mesh.indices.size(), 0); - tbb::parallel_for(tbb::blocked_range(0, dst_mesh.indices.size()), - [&](const tbb::blocked_range &range) { - for (size_t j = range.begin(); j < range.end(); ++j) { - const stl_triangle_vertex_indices &t = dst_mesh.indices[j]; - // The centroid, not a corner: a facet takes one state, and its middle is the point that - // is inside it whatever the triangle looks like. - const Vec3f centroid = (dst_mesh.vertices[size_t(t[0])] + dst_mesh.vertices[size_t(t[1])] + - dst_mesh.vertices[size_t(t[2])]) / 3.f; - size_t hit = 0; - Vec3f foot; - const float d2 = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(soup.vertices, soup.indices, - tree, centroid, hit, foot); - if (d2 >= 0.f && hit < label.size()) - out[j] = label[hit]; - } - }); - return out; -} - namespace { // apply_uv_transform()'s per-layer constants, worked out once. Triplanar sampling runs the transform // three times per point, and recomputing the rotation's cos/sin and the tiling reciprocal on every one @@ -1990,8 +1990,7 @@ float sample_layer_height(const DecodedHeightTexture &texture, const TextureDisp // Flat projection onto the captured projector plane. Single-valued per point, so unlike // blended triplanar it is one sample, and it is what "project from view" places. return sample_at(Vec2f(position.dot(layer.view_project_right), position.dot(layer.view_project_up))); - case TextureProjectionMethod::LSCM: // no usable unwrap for this patch - fall back to Triplanar - case TextureProjectionMethod::ModelUV: // the volume carries no UV channel - same fallback + case TextureProjectionMethod::LSCM: // no usable unwrap for this patch - fall back to Triplanar case TextureProjectionMethod::Triplanar: default: break; } @@ -2086,8 +2085,7 @@ bool sample_layer_color(const DecodedHeightTexture &texture, const TextureDispla out = sample_at(p); return true; } - case TextureProjectionMethod::LSCM: // no usable unwrap for this patch - fall back to Triplanar - case TextureProjectionMethod::ModelUV: // the volume carries no UV channel - same fallback + case TextureProjectionMethod::LSCM: // no usable unwrap for this patch - fall back to Triplanar case TextureProjectionMethod::Triplanar: default: break; } diff --git a/src/libslic3r/TextureDisplacement.hpp b/src/libslic3r/TextureDisplacement.hpp index 2c5b03e855..06c655b070 100644 --- a/src/libslic3r/TextureDisplacement.hpp +++ b/src/libslic3r/TextureDisplacement.hpp @@ -600,6 +600,31 @@ bool sample_layer_color(const DecodedHeightTexture &texture, const TextureDispla const Vec3f &patch_center = Vec3f::Zero(), const Vec3f &patch_axis = Vec3f::UnitZ(), const Vec2f *lscm_uv = nullptr); +// Carries a per-facet paint annotation - the colour paint above all - from a mesh onto the mesh a +// bake produced from it: every triangle of `dst_mesh` takes the state painted at its own foot point on +// `src_mesh`, i.e. on the nearest point of the surface it was built from. Returned as one +// EnforcerBlockerType per `dst_mesh` triangle, 0 (NONE) where that foot point carried no paint. +// +// ModelVolume::restore_painting() cannot do this. Its remap pairs a source paint triangle with a +// target triangle that *overlaps* it to within 0.01 mm, which is right for a remesh or a +// simplification - they leave the surface where it was - and useless after a bake, which moves the +// surface by the relief depth. Measured on a model displaced by a tenth of its own size, that remap +// kept a quarter of the colour and left the rest unpainted; this keeps essentially all of it, because +// a foot point is exactly as far away as the relief is deep and the lookup does not care how far that +// is. +// +// The source's *sub*-triangles are what is looked up, not its triangles, so the part of a triangle a +// brush stroke only clipped a corner off keeps its own state rather than rounding to the whole +// triangle's. No distance limit is needed or wanted: the nearest point on the source surface is where +// a baked triangle came from however far the relief carried it, and an unpainted origin reports NONE +// on its own. +// +// Returns an empty vector when `src_paint` is empty or either mesh is - the caller then has nothing to +// carry, which is not the same as "carry nothing". +std::vector transfer_facet_paint(const indexed_triangle_set &src_mesh, + const TriangleSelector::TriangleSplittingData &src_paint, + const indexed_triangle_set &dst_mesh); + // Area-weighted centroid and average normal of a layer's currently painted patch, in mesh-local // coordinates - the same measurements build_texture_displacement() uses to pick its dominant // projection axis. Used by the GUI to anchor the on-canvas "adjust texture placement" gizmo to @@ -762,31 +787,6 @@ std::vector transfer_uv_channel(const indexed_triangle_set &src_mesh, const indexed_triangle_set &dst_mesh, float max_distance = -1.f); -// Carries a per-facet paint annotation - the colour paint above all - from a mesh onto the mesh a -// bake produced from it: every triangle of `dst_mesh` takes the state painted at its own foot point on -// `src_mesh`, i.e. on the nearest point of the surface it was built from. Returned as one -// EnforcerBlockerType per `dst_mesh` triangle, 0 (NONE) where that foot point carried no paint. -// -// ModelVolume::restore_painting() cannot do this. Its remap pairs a source paint triangle with a -// target triangle that *overlaps* it to within 0.01 mm, which is right for a remesh or a -// simplification - they leave the surface where it was - and useless after a bake, which moves the -// surface by the relief depth. Measured on a model displaced by a tenth of its own size, that remap -// kept a quarter of the colour and left the rest unpainted; this keeps essentially all of it, because -// a foot point is exactly as far away as the relief is deep and the lookup does not care how far that -// is. -// -// The source's *sub*-triangles are what is looked up, not its triangles, so the part of a triangle a -// brush stroke only clipped a corner off keeps its own state rather than rounding to the whole -// triangle's. No distance limit is needed or wanted: the nearest point on the source surface is where -// a baked triangle came from however far the relief carried it, and an unpainted origin reports NONE -// on its own. -// -// Returns an empty vector when `src_paint` is empty or either mesh is - the caller then has nothing to -// carry, which is not the same as "carry nothing". -std::vector transfer_facet_paint(const indexed_triangle_set &src_mesh, - const TriangleSelector::TriangleSplittingData &src_paint, - const indexed_triangle_set &dst_mesh); - // The TextureDisplacementLayer::lscm_uv_overrides key for one unwrapped vertex (an index into PatchUnwrap::uvs). inline int lscm_uv_override_key(int unwrapped_vertex) { return -(unwrapped_vertex + 1); } diff --git a/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp b/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp index f0a3f56880..72363e4393 100644 --- a/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp +++ b/src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp @@ -188,20 +188,12 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept } } - // The model's own UV channel. Resampled onto a rebuilt mesh from the surface the bake started - // from: a displaced vertex came from its foot point on that surface, which is the position its - // uv belongs to - so no distance limit, the relief depth is exactly how far it has moved. - if (!m_input.model_uv.empty()) - volume->texture_uv = topology_changed ? transfer_uv_channel(m_input.base_mesh, m_input.model_uv, - volume->mesh().its) : - m_input.model_uv; - // Colour lands in mmu_segmentation_facets, merged *over* whatever is already painted there // rather than replacing it: a triangle the texture does not colour keeps its existing filament, // and one the user never painted at all stays at NONE, which already means "the volume's own // filament". That is what confines the effect to the painted area without having to invent a - // colour for everything outside it. Safe to index straight onto the new mesh - the bake is - // topology-preserving, so triangle i is still triangle i. + // colour for everything outside it. What was already painted is whatever the transfer above + // left in the annotation, so this bake's colour lands on top of it rather than instead of it. if (!m_triangle_color.empty() && m_triangle_color.size() == volume->mesh().its.indices.size()) { TriangleSelector selector(volume->mesh()); const TriangleSelector::TriangleSplittingData &existing = volume->mmu_segmentation_facets.get_data(); @@ -232,6 +224,14 @@ void TextureDisplacementBakeJob::finalize(bool canceled, std::exception_ptr &ept volume->texture_displacement_facet(slot).reset(); } + // The model's own UV channel. Resampled onto a rebuilt mesh from the surface the bake started + // from: a displaced vertex came from its foot point on that surface, which is the position its + // uv belongs to - so no distance limit, the relief depth is exactly how far it has moved. + if (!m_input.model_uv.empty()) + volume->texture_uv = topology_changed ? transfer_uv_channel(m_input.base_mesh, m_input.model_uv, + volume->mesh().its) : + m_input.model_uv; + ModelObject *object = volume->get_object(); if (object == nullptr) return;