mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-27 21:02:23 +00:00
POC
This commit is contained in:
@@ -442,6 +442,8 @@ set(lisbslic3r_sources
|
||||
Tesselate.cpp
|
||||
Tesselate.hpp
|
||||
TextConfiguration.hpp
|
||||
TextureDisplacement.cpp
|
||||
TextureDisplacement.hpp
|
||||
Thread.cpp
|
||||
Thread.hpp
|
||||
Time.cpp
|
||||
|
||||
@@ -1977,6 +1977,11 @@ void ModelVolume::reset_extra_facets()
|
||||
this->seam_facets.reset();
|
||||
this->mmu_segmentation_facets.reset();
|
||||
this->fuzzy_skin_facets.reset();
|
||||
// Texture-displacement paint data has no remap-across-topology-change support yet (see
|
||||
// build_texture_displacement()'s documented limitation), so it must be dropped here rather
|
||||
// than left referring to a mesh that no longer matches it.
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
this->texture_displacement_facet(i).reset();
|
||||
}
|
||||
|
||||
std::optional<TriangleSelector::SavedPainting> ModelVolume::save_painting() const
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "TextConfiguration.hpp"
|
||||
#include "EmbossShape.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "TextureDisplacement.hpp"
|
||||
|
||||
//BBS: add bbs 3mf
|
||||
#include "Format/bbs_3mf.hpp"
|
||||
@@ -28,6 +29,7 @@
|
||||
#include "Format/STL.hpp"
|
||||
#include "Format/OBJ.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -878,6 +880,64 @@ public:
|
||||
// List of mesh facets painted for fuzzy skin.
|
||||
FacetsAnnotation fuzzy_skin_facets;
|
||||
|
||||
// One independent paint mask per texture-displacement layer slot (see texture_displacement_layers
|
||||
// below). Unlike the other facets fields above, a triangle may be painted (ENFORCER) in more
|
||||
// than one of these simultaneously -- that overlap is what makes the layers "blend".
|
||||
//
|
||||
// These are 8 plain named fields rather than a std::array<FacetsAnnotation, N>: FacetsAnnotation's
|
||||
// default/copy constructors are private and friended only to ModelVolume, but std::array's own
|
||||
// implicitly-defined default/copy constructors are generated with std::array's access rights,
|
||||
// not ModelVolume's -- so an array of FacetsAnnotation ends up with its default/copy
|
||||
// constructors implicitly deleted regardless of the friend declaration. Use
|
||||
// texture_displacement_facet(slot) below for array-like indexed access.
|
||||
FacetsAnnotation texture_displacement_facets_0;
|
||||
FacetsAnnotation texture_displacement_facets_1;
|
||||
FacetsAnnotation texture_displacement_facets_2;
|
||||
FacetsAnnotation texture_displacement_facets_3;
|
||||
FacetsAnnotation texture_displacement_facets_4;
|
||||
FacetsAnnotation texture_displacement_facets_5;
|
||||
FacetsAnnotation texture_displacement_facets_6;
|
||||
FacetsAnnotation texture_displacement_facets_7;
|
||||
|
||||
FacetsAnnotation& texture_displacement_facet(int slot) {
|
||||
switch (slot) {
|
||||
case 0: return texture_displacement_facets_0;
|
||||
case 1: return texture_displacement_facets_1;
|
||||
case 2: return texture_displacement_facets_2;
|
||||
case 3: return texture_displacement_facets_3;
|
||||
case 4: return texture_displacement_facets_4;
|
||||
case 5: return texture_displacement_facets_5;
|
||||
case 6: return texture_displacement_facets_6;
|
||||
default: assert(slot == 7); return texture_displacement_facets_7;
|
||||
}
|
||||
}
|
||||
const FacetsAnnotation& texture_displacement_facet(int slot) const { return const_cast<ModelVolume*>(this)->texture_displacement_facet(slot); }
|
||||
|
||||
// Small helpers for the constructor asserts below (kept out of line-noise at each call site).
|
||||
bool texture_displacement_facets_ids_valid() const {
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
if (!texture_displacement_facet(i).id().valid() || texture_displacement_facet(i).id() == this->id())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
bool texture_displacement_facets_ids_invalid() const {
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
if (texture_displacement_facet(i).id().valid())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
bool texture_displacement_facets_all_empty() const {
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
if (!texture_displacement_facet(i).empty())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Texture assets (height maps) and their projection/displacement parameters. Element order
|
||||
// is not meaningful for baking (layers are applied in TextureDisplacementLayer::slot order,
|
||||
// see build_texture_displacement()); it only reflects UI insertion order.
|
||||
std::vector<TextureDisplacementLayer> texture_displacement_layers;
|
||||
|
||||
// Save painting data before reset_extra_facets() discards it.
|
||||
// Used for replacing mesh without losing painting data.
|
||||
// Only for model parts (not modifiers/connectors).
|
||||
@@ -1007,13 +1067,18 @@ public:
|
||||
this->seam_facets.set_new_unique_id();
|
||||
this->mmu_segmentation_facets.set_new_unique_id();
|
||||
this->fuzzy_skin_facets.set_new_unique_id();
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
this->texture_displacement_facet(i).set_new_unique_id();
|
||||
}
|
||||
|
||||
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
|
||||
bool is_seam_painted() const { return !this->seam_facets.empty(); }
|
||||
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
|
||||
bool is_fuzzy_skin_painted() const { return !this->fuzzy_skin_facets.empty(); }
|
||||
bool is_any_painted() const { return is_fdm_support_painted() || is_seam_painted() || is_mm_painted() || is_fuzzy_skin_painted(); }
|
||||
bool is_texture_displacement_painted() const { return !this->texture_displacement_facets_all_empty(); }
|
||||
bool is_any_painted() const {
|
||||
return is_fdm_support_painted() || is_seam_painted() || is_mm_painted() || is_fuzzy_skin_painted() || is_texture_displacement_painted();
|
||||
}
|
||||
|
||||
// Orca: Implement prusa's filament shrink compensation approach
|
||||
// Returns 0-based indices of extruders painted by multi-material painting gizmo.
|
||||
@@ -1066,6 +1131,7 @@ private:
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->fuzzy_skin_facets.id().valid());
|
||||
assert(this->texture_displacement_facets_ids_valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
@@ -1082,6 +1148,7 @@ private:
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->fuzzy_skin_facets.id().valid());
|
||||
assert(this->texture_displacement_facets_ids_valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
@@ -1096,6 +1163,7 @@ private:
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
assert(this->fuzzy_skin_facets.id().valid());
|
||||
assert(this->texture_displacement_facets_ids_valid());
|
||||
assert(this->id() != this->config.id());
|
||||
assert(this->id() != this->supported_facets.id());
|
||||
assert(this->id() != this->seam_facets.id());
|
||||
@@ -1109,10 +1177,16 @@ private:
|
||||
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
|
||||
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
|
||||
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
|
||||
fuzzy_skin_facets(other.fuzzy_skin_facets), cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
|
||||
fuzzy_skin_facets(other.fuzzy_skin_facets),
|
||||
texture_displacement_facets_0(other.texture_displacement_facets_0), texture_displacement_facets_1(other.texture_displacement_facets_1),
|
||||
texture_displacement_facets_2(other.texture_displacement_facets_2), texture_displacement_facets_3(other.texture_displacement_facets_3),
|
||||
texture_displacement_facets_4(other.texture_displacement_facets_4), texture_displacement_facets_5(other.texture_displacement_facets_5),
|
||||
texture_displacement_facets_6(other.texture_displacement_facets_6), texture_displacement_facets_7(other.texture_displacement_facets_7),
|
||||
texture_displacement_layers(other.texture_displacement_layers),
|
||||
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
|
||||
{
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->id().valid());
|
||||
assert(this->config.id().valid());
|
||||
assert(this->supported_facets.id().valid());
|
||||
assert(this->seam_facets.id().valid());
|
||||
assert(this->mmu_segmentation_facets.id().valid());
|
||||
@@ -1162,6 +1236,8 @@ private:
|
||||
assert(this->seam_facets.empty());
|
||||
assert(this->mmu_segmentation_facets.empty());
|
||||
assert(this->fuzzy_skin_facets.empty());
|
||||
assert(this->texture_displacement_facets_all_empty());
|
||||
assert(this->texture_displacement_layers.empty());
|
||||
}
|
||||
|
||||
ModelVolume& operator=(ModelVolume &rhs) = delete;
|
||||
@@ -1169,13 +1245,17 @@ private:
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
// Used for deserialization, therefore no IDs are allocated.
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), fuzzy_skin_facets(-1), object(nullptr) {
|
||||
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), fuzzy_skin_facets(-1),
|
||||
texture_displacement_facets_0(-1), texture_displacement_facets_1(-1), texture_displacement_facets_2(-1), texture_displacement_facets_3(-1),
|
||||
texture_displacement_facets_4(-1), texture_displacement_facets_5(-1), texture_displacement_facets_6(-1), texture_displacement_facets_7(-1),
|
||||
object(nullptr) {
|
||||
assert(this->id().invalid());
|
||||
assert(this->config.id().invalid());
|
||||
assert(this->supported_facets.id().invalid());
|
||||
assert(this->seam_facets.id().invalid());
|
||||
assert(this->mmu_segmentation_facets.id().invalid());
|
||||
assert(this->fuzzy_skin_facets.id().invalid());
|
||||
assert(this->texture_displacement_facets_ids_invalid());
|
||||
}
|
||||
template<class Archive> void load(Archive &ar) {
|
||||
bool has_convex_hull;
|
||||
@@ -1195,6 +1275,13 @@ private:
|
||||
mesh_changed |= t != mmu_segmentation_facets.timestamp();
|
||||
cereal::load_by_value(ar, fuzzy_skin_facets);
|
||||
mesh_changed |= t != fuzzy_skin_facets.timestamp();
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i) {
|
||||
FacetsAnnotation &f = texture_displacement_facet(i);
|
||||
Timestamp tf = f.timestamp();
|
||||
cereal::load_by_value(ar, f);
|
||||
mesh_changed |= tf != f.timestamp();
|
||||
}
|
||||
ar(texture_displacement_layers);
|
||||
cereal::load_by_value(ar, config);
|
||||
cereal::load(ar, text_configuration);
|
||||
cereal::load(ar, emboss_shape);
|
||||
@@ -1216,6 +1303,9 @@ private:
|
||||
cereal::save_by_value(ar, seam_facets);
|
||||
cereal::save_by_value(ar, mmu_segmentation_facets);
|
||||
cereal::save_by_value(ar, fuzzy_skin_facets);
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
cereal::save_by_value(ar, texture_displacement_facet(i));
|
||||
ar(texture_displacement_layers);
|
||||
cereal::save_by_value(ar, config);
|
||||
cereal::save(ar, text_configuration);
|
||||
cereal::save(ar, emboss_shape);
|
||||
|
||||
226
src/libslic3r/TextureDisplacement.cpp
Normal file
226
src/libslic3r/TextureDisplacement.cpp
Normal file
@@ -0,0 +1,226 @@
|
||||
#include "TextureDisplacement.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <optional>
|
||||
|
||||
#include "Model.hpp"
|
||||
#include "PNGReadWrite.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
float DecodedHeightTexture::sample(const Vec2f &uv) const
|
||||
{
|
||||
if (empty())
|
||||
return 0.f;
|
||||
|
||||
auto wrap01 = [](float x) {
|
||||
x = std::fmod(x, 1.f);
|
||||
return x < 0.f ? x + 1.f : x;
|
||||
};
|
||||
|
||||
const float fx = wrap01(uv.x()) * float(width);
|
||||
const float fy = wrap01(uv.y()) * float(height);
|
||||
int x0 = int(std::floor(fx)) % width;
|
||||
int y0 = int(std::floor(fy)) % height;
|
||||
if (x0 < 0) x0 += width;
|
||||
if (y0 < 0) y0 += height;
|
||||
const int x1 = (x0 + 1) % width;
|
||||
const int y1 = (y0 + 1) % height;
|
||||
const float tx = fx - std::floor(fx);
|
||||
const float ty = fy - std::floor(fy);
|
||||
|
||||
auto at = [this](int x, int y) { return float(pixels[size_t(y) * size_t(width) + size_t(x)]) / 255.f; };
|
||||
const float top = at(x0, y0) * (1.f - tx) + at(x1, y0) * tx;
|
||||
const float bottom = at(x0, y1) * (1.f - tx) + at(x1, y1) * tx;
|
||||
return top * (1.f - ty) + bottom * ty;
|
||||
}
|
||||
|
||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer)
|
||||
{
|
||||
DecodedHeightTexture result;
|
||||
if (layer.empty())
|
||||
return result;
|
||||
|
||||
const png::ReadBuf rbuf{ layer.image_data->data(), layer.image_data->size() };
|
||||
if (!png::is_png(rbuf))
|
||||
// Only 8-bit grayscale PNG height maps are supported. The GUI is responsible for
|
||||
// converting any imported image (jpg, color png, ...) to that format on import, so this
|
||||
// code never needs a dependency on wxWidgets/libjpeg to decode arbitrary user images.
|
||||
return result;
|
||||
|
||||
png::ImageGreyscale img;
|
||||
if (!png::decode_png(rbuf, img) || img.cols == 0 || img.rows == 0)
|
||||
return result;
|
||||
|
||||
result.width = int(img.cols);
|
||||
result.height = int(img.rows);
|
||||
result.pixels = std::move(img.buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
Vec2f project_texture_displacement_uv(const Vec3f &position, const Vec3f &normal, const TextureDisplacementLayer &layer)
|
||||
{
|
||||
// Planar-project onto the two axes orthogonal to the dominant component of `normal`. Using a
|
||||
// single projection axis for an entire patch (rather than per-vertex normals) avoids visible
|
||||
// seams where the local normal direction changes. Proper seam-aware UV parametrization is a
|
||||
// later-phase improvement.
|
||||
const Vec3f n = normal.cwiseAbs();
|
||||
Vec2f planar;
|
||||
if (n.x() >= n.y() && n.x() >= n.z())
|
||||
planar = Vec2f(position.y(), position.z());
|
||||
else if (n.y() >= n.x() && n.y() >= n.z())
|
||||
planar = Vec2f(position.x(), position.z());
|
||||
else
|
||||
planar = Vec2f(position.x(), position.y());
|
||||
|
||||
const float scale = (layer.tiling_scale > 1e-6f) ? (1.f / layer.tiling_scale) : 1.f;
|
||||
planar *= scale;
|
||||
|
||||
const float rad = layer.rotation_deg * float(M_PI) / 180.f;
|
||||
const float cs = std::cos(rad);
|
||||
const float sn = std::sin(rad);
|
||||
const Vec2f rotated(planar.x() * cs - planar.y() * sn, planar.x() * sn + planar.y() * cs);
|
||||
|
||||
return rotated + layer.offset;
|
||||
}
|
||||
|
||||
// Area-weighted vertex normals computed from the patch's own (pre-displacement) connectivity.
|
||||
// Since the patch's vertices have not moved yet at the point this is called, these are identical
|
||||
// to the surface normals of the mesh the patch was extracted from.
|
||||
static std::vector<Vec3f> texture_displacement_vertex_normals(const indexed_triangle_set &its)
|
||||
{
|
||||
std::vector<Vec3f> normals(its.vertices.size(), Vec3f::Zero());
|
||||
for (const stl_triangle_vertex_indices &tri : its.indices) {
|
||||
const Vec3f &a = its.vertices[tri[0]];
|
||||
const Vec3f &b = its.vertices[tri[1]];
|
||||
const Vec3f &c = its.vertices[tri[2]];
|
||||
// Cross product magnitude is twice the face area, so this naturally area-weights the
|
||||
// contribution of each incident face to its vertices.
|
||||
const Vec3f area_weighted_normal = (b - a).cross(c - a);
|
||||
normals[tri[0]] += area_weighted_normal;
|
||||
normals[tri[1]] += area_weighted_normal;
|
||||
normals[tri[2]] += area_weighted_normal;
|
||||
}
|
||||
for (Vec3f &n : normals) {
|
||||
const float len = n.norm();
|
||||
n = (len > 1e-8f) ? Vec3f(n / len) : Vec3f::UnitZ();
|
||||
}
|
||||
return normals;
|
||||
}
|
||||
|
||||
indexed_triangle_set build_texture_displacement(const indexed_triangle_set &base_mesh,
|
||||
const std::vector<TextureDisplacementLayer> &layers,
|
||||
const TextureDisplacementFacetsData &facets_data)
|
||||
{
|
||||
const indexed_triangle_set original_mesh = base_mesh;
|
||||
indexed_triangle_set working_mesh = original_mesh;
|
||||
bool mesh_modified = false;
|
||||
|
||||
// Layers behave like stacked image-editor layers: applied in slot order, each one sculpting
|
||||
// the surface left by the layers before it. This is what makes overlapping layers "blend".
|
||||
std::vector<const TextureDisplacementLayer *> ordered_layers;
|
||||
for (const TextureDisplacementLayer &layer : layers)
|
||||
if (!layer.empty() && layer.slot >= 0 && size_t(layer.slot) < TEXTURE_DISPLACEMENT_MAX_LAYERS)
|
||||
ordered_layers.push_back(&layer);
|
||||
std::sort(ordered_layers.begin(), ordered_layers.end(),
|
||||
[](const TextureDisplacementLayer *a, const TextureDisplacementLayer *b) { return a->slot < b->slot; });
|
||||
|
||||
for (const TextureDisplacementLayer *layer : ordered_layers) {
|
||||
const TriangleSelector::TriangleSplittingData &stored_data = facets_data[size_t(layer->slot)];
|
||||
if (stored_data.triangles_to_split.empty())
|
||||
continue;
|
||||
|
||||
const DecodedHeightTexture height = decode_height_texture(*layer);
|
||||
if (height.empty())
|
||||
continue;
|
||||
|
||||
TriangleSelector::TriangleSplittingData data = stored_data;
|
||||
if (mesh_modified) {
|
||||
// The stored paint mask is relative to the volume's original (unbaked) mesh; remap it
|
||||
// onto the working mesh, which earlier layers may have already displaced/subdivided.
|
||||
data = TriangleSelector::remap_painting(original_mesh, data, working_mesh, Transform3d::Identity(),
|
||||
std::optional<std::reference_wrapper<const TriangleSelector::TriangleSplittingData>>{});
|
||||
if (data.bitstream.empty())
|
||||
continue;
|
||||
}
|
||||
|
||||
const TriangleMesh selector_mesh(working_mesh);
|
||||
TriangleSelector selector(selector_mesh);
|
||||
selector.deserialize(data, false);
|
||||
|
||||
const indexed_triangle_set patch = selector.get_facets_strict(EnforcerBlockerType::ENFORCER);
|
||||
if (patch.indices.empty())
|
||||
continue;
|
||||
// get_facets_strict() returns the *entire* mesh's vertex array regardless of which state
|
||||
// was asked for (only the returned triangle indices are filtered by state) -- so `patch`
|
||||
// and `rest` share the exact same vertex indexing. That is what lets the weld below be a
|
||||
// plain index check instead of a position-based lookup.
|
||||
indexed_triangle_set rest = selector.get_facets_strict(EnforcerBlockerType::NONE);
|
||||
assert(rest.vertices.size() == patch.vertices.size());
|
||||
|
||||
// A vertex that is also used by at least one *unpainted* triangle is a boundary vertex:
|
||||
// its final position is ambiguous (it belongs to both the painted and untouched surface),
|
||||
// so it must never be displaced -- otherwise the baked patch would tear away from the
|
||||
// rest of the mesh. Only vertices used exclusively by painted triangles ("interior" to the
|
||||
// patch) are eligible for displacement. This is what keeps the bake seamless without any
|
||||
// remeshing/hole-filling at the boundary.
|
||||
std::vector<bool> is_boundary_vertex(rest.vertices.size(), false);
|
||||
for (const stl_triangle_vertex_indices &tri : rest.indices)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
is_boundary_vertex[tri[i]] = true;
|
||||
|
||||
const std::vector<Vec3f> vertex_normals = texture_displacement_vertex_normals(patch);
|
||||
|
||||
Vec3f average_normal = Vec3f::Zero();
|
||||
for (const stl_triangle_vertex_indices &tri : patch.indices)
|
||||
for (int i = 0; i < 3; ++i)
|
||||
average_normal += vertex_normals[tri[i]];
|
||||
average_normal = (average_normal.norm() > 1e-8f) ? Vec3f(average_normal.normalized()) : Vec3f::UnitZ();
|
||||
|
||||
const float sign = layer->invert ? -1.f : 1.f;
|
||||
std::vector<int> interior_vertex_remap(rest.vertices.size(), -1);
|
||||
indexed_triangle_set working_mesh_next = std::move(rest);
|
||||
for (const stl_triangle_vertex_indices &tri : patch.indices) {
|
||||
stl_triangle_vertex_indices final_tri;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const int vi = tri[i];
|
||||
if (is_boundary_vertex[vi]) {
|
||||
// Shared with the untouched surface: reuse the existing, undisplaced vertex.
|
||||
final_tri[i] = vi;
|
||||
continue;
|
||||
}
|
||||
if (interior_vertex_remap[vi] < 0) {
|
||||
const Vec2f uv = project_texture_displacement_uv(patch.vertices[vi], average_normal, *layer);
|
||||
const float h = height.sample(uv);
|
||||
const Vec3f displaced = patch.vertices[vi] + vertex_normals[vi] * (h * layer->depth_mm * sign);
|
||||
interior_vertex_remap[vi] = int(working_mesh_next.vertices.size());
|
||||
working_mesh_next.vertices.push_back(displaced);
|
||||
}
|
||||
final_tri[i] = interior_vertex_remap[vi];
|
||||
}
|
||||
working_mesh_next.indices.push_back(final_tri);
|
||||
}
|
||||
|
||||
working_mesh = std::move(working_mesh_next);
|
||||
mesh_modified = true;
|
||||
}
|
||||
|
||||
if (mesh_modified)
|
||||
its_compactify_vertices(working_mesh);
|
||||
|
||||
return working_mesh;
|
||||
}
|
||||
|
||||
indexed_triangle_set build_texture_displacement(const ModelVolume &volume)
|
||||
{
|
||||
TextureDisplacementFacetsData facets_data;
|
||||
for (int i = 0; i < int(TEXTURE_DISPLACEMENT_MAX_LAYERS); ++i)
|
||||
facets_data[size_t(i)] = volume.texture_displacement_facet(i).get_data();
|
||||
|
||||
return build_texture_displacement(volume.mesh().its, volume.texture_displacement_layers, facets_data);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
120
src/libslic3r/TextureDisplacement.hpp
Normal file
120
src/libslic3r/TextureDisplacement.hpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef slic3r_TextureDisplacement_hpp_
|
||||
#define slic3r_TextureDisplacement_hpp_
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <cereal/cereal.hpp>
|
||||
#include <cereal/types/string.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelVolume;
|
||||
|
||||
// Maximum number of simultaneous texture-displacement layers a single ModelVolume can hold.
|
||||
// Each layer owns its own paint mask (ModelVolume::texture_displacement_facet(slot)), so this
|
||||
// is also the number of independent EnforcerBlockerType selectors kept per volume.
|
||||
static constexpr size_t TEXTURE_DISPLACEMENT_MAX_LAYERS = 8;
|
||||
|
||||
// One texture asset plus its projection/displacement parameters. Several layers may be painted
|
||||
// onto overlapping areas of the same volume: they are applied in slot order (like layers in an
|
||||
// image editor), each one displacing the surface that resulted from the layers before it. This
|
||||
// is what "layered/blended" texture displacement means in this feature.
|
||||
struct TextureDisplacementLayer
|
||||
{
|
||||
// Index into ModelVolume::texture_displacement_facets, assigned once when the layer is
|
||||
// created. Not reused for the lifetime of the ModelVolume, so a deleted layer's slot simply
|
||||
// becomes unused rather than being handed to a different layer.
|
||||
int slot = -1;
|
||||
|
||||
std::string name;
|
||||
// Path on the local filesystem the image was loaded from (informational; may be stale or
|
||||
// empty, e.g. after loading a .3mf on a different machine).
|
||||
std::string path;
|
||||
// Path inside the .3mf archive once saved (empty until the project is saved once).
|
||||
std::string path_in_3mf;
|
||||
// Raw encoded image bytes. Only 8-bit grayscale PNG is understood by decode_height_texture()
|
||||
// (libslic3r has no GUI image toolkit available); the GUI converts any imported image to that
|
||||
// format before storing it here, so the baking code never needs to depend on wxWidgets.
|
||||
std::shared_ptr<std::vector<unsigned char>> image_data;
|
||||
|
||||
float depth_mm = 0.4f; // maximum displacement along the surface normal, in mm
|
||||
float tiling_scale = 10.f; // size of one texture tile, in mm
|
||||
float rotation_deg = 0.f;
|
||||
Vec2f offset = Vec2f::Zero();
|
||||
bool invert = false;
|
||||
|
||||
bool empty() const { return !image_data || image_data->empty(); }
|
||||
|
||||
template<class Archive> void save(Archive &ar) const
|
||||
{
|
||||
std::string blob = image_data ? std::string(image_data->begin(), image_data->end()) : std::string();
|
||||
ar(slot, name, path, path_in_3mf, blob, depth_mm, tiling_scale, rotation_deg, offset, invert);
|
||||
}
|
||||
template<class Archive> void load(Archive &ar)
|
||||
{
|
||||
std::string blob;
|
||||
ar(slot, name, path, path_in_3mf, blob, depth_mm, tiling_scale, rotation_deg, offset, invert);
|
||||
image_data = blob.empty() ? nullptr : std::make_shared<std::vector<unsigned char>>(blob.begin(), blob.end());
|
||||
}
|
||||
};
|
||||
|
||||
// Decoded 8-bit grayscale height sample, independent of any GUI/OpenGL texture object so it can
|
||||
// be evaluated from a background bake Job as well as from GUI-side preview code.
|
||||
struct DecodedHeightTexture
|
||||
{
|
||||
std::vector<uint8_t> pixels; // row-major, top-to-bottom, one byte per pixel
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
||||
bool empty() const { return width <= 0 || height <= 0 || pixels.empty(); }
|
||||
// Bilinearly sampled height in [0, 1] at a tiling (wrapping) normalized uv coordinate.
|
||||
float sample(const Vec2f &uv) const;
|
||||
};
|
||||
|
||||
// Decode a layer's raw image bytes into sampleable grayscale height data. Returns an empty
|
||||
// DecodedHeightTexture if image_data is empty or is not an 8-bit grayscale PNG.
|
||||
DecodedHeightTexture decode_height_texture(const TextureDisplacementLayer &layer);
|
||||
|
||||
// Project a mesh-local position onto a layer's height texture, honouring its tiling scale,
|
||||
// rotation and offset. Uses a simple dominant-axis planar projection of the given normal: proper
|
||||
// seam-aware UV parametrization is a later-phase improvement (see project plan).
|
||||
Vec2f project_texture_displacement_uv(const Vec3f &position, const Vec3f &normal, const TextureDisplacementLayer &layer);
|
||||
|
||||
// One paint mask (as stored by ModelVolume::texture_displacement_facets) per possible layer slot.
|
||||
using TextureDisplacementFacetsData = std::array<TriangleSelector::TriangleSplittingData, TEXTURE_DISPLACEMENT_MAX_LAYERS>;
|
||||
|
||||
// Bake all painted texture-displacement layers into `base_mesh`'s geometry, restricted to the
|
||||
// painted area(s) only (the rest of the mesh is left untouched). Layers are applied in slot
|
||||
// order, each measuring its displacement from the surface left by the previous one. Returns the
|
||||
// mesh unchanged if nothing is painted or no layer has a usable texture.
|
||||
//
|
||||
// Takes plain copied data rather than a ModelVolume reference so it is safe to call from a
|
||||
// background thread (e.g. a bake Job's process() method) on a snapshot captured on the main
|
||||
// thread, without touching the live Model concurrently with the UI.
|
||||
//
|
||||
// Known Phase 1-3 limitation: this does not attempt to remap texture-displacement paint data
|
||||
// across topology-changing operations performed outside this gizmo (e.g. ModelObject::split(),
|
||||
// mesh-boolean ops) the way TriangleSelector::remap_painting() does for the other paint channels.
|
||||
// Such operations will silently drop any unbaked texture-displacement paint on the affected
|
||||
// volume. This is an explicit extension point for a later phase, not an oversight.
|
||||
indexed_triangle_set build_texture_displacement(const indexed_triangle_set &base_mesh,
|
||||
const std::vector<TextureDisplacementLayer> &layers,
|
||||
const TextureDisplacementFacetsData &facets_data);
|
||||
|
||||
// Convenience overload for main-thread callers: extracts the mesh/layers/paint data from `volume`
|
||||
// and forwards to the overload above.
|
||||
indexed_triangle_set build_texture_displacement(const ModelVolume &volume);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_TextureDisplacement_hpp_
|
||||
Reference in New Issue
Block a user