mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-17 10:32:20 +00:00
Remapping the paintings after cut
This commit is contained in:
@@ -8,11 +8,39 @@
|
||||
#include "ObjectID.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using namespace Geometry;
|
||||
|
||||
// Saved painting data for remapping after mesh cutting.
|
||||
struct SavedPainting {
|
||||
indexed_triangle_set its; // Original mesh transformed to cut space
|
||||
TriangleSelector::TriangleSplittingData supported;
|
||||
TriangleSelector::TriangleSplittingData seam;
|
||||
TriangleSelector::TriangleSplittingData mmu;
|
||||
TriangleSelector::TriangleSplittingData fuzzy;
|
||||
};
|
||||
|
||||
// Remap painting data from saved source to a cut result mesh, and set on a volume.
|
||||
static void remap_and_set_painting(ModelVolume* vol, const indexed_triangle_set& cut_its,
|
||||
const SavedPainting& saved)
|
||||
{
|
||||
auto remap_one = [&](const TriangleSelector::TriangleSplittingData& src_data,
|
||||
FacetsAnnotation& target_facets) {
|
||||
if (src_data.bitstream.empty())
|
||||
return;
|
||||
auto result = TriangleSelector::remap_painting(saved.its, src_data, cut_its);
|
||||
if (!result.bitstream.empty())
|
||||
target_facets.set_data(result);
|
||||
};
|
||||
remap_one(saved.supported, vol->supported_facets);
|
||||
remap_one(saved.seam, vol->seam_facets);
|
||||
remap_one(saved.mmu, vol->mmu_segmentation_facets);
|
||||
remap_one(saved.fuzzy, vol->fuzzy_skin_facets);
|
||||
}
|
||||
|
||||
static void apply_tolerance(ModelVolume* vol)
|
||||
{
|
||||
ModelVolume::CutInfo& cut_info = vol->cut_info;
|
||||
@@ -179,7 +207,8 @@ static void process_modifier_cut(ModelVolume* volume, const Transform3d& instanc
|
||||
}
|
||||
|
||||
static void process_solid_part_cut(ModelVolume* volume, const Transform3d& instance_matrix, const Transform3d& cut_matrix,
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower)
|
||||
ModelObjectCutAttributes attributes, ModelObject* upper, ModelObject* lower,
|
||||
const SavedPainting* saved_painting = nullptr)
|
||||
{
|
||||
// Perform cut
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
@@ -189,18 +218,28 @@ static void process_solid_part_cut(ModelVolume* volume, const Transform3d& insta
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepAsParts)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix, "_A");
|
||||
if (saved_painting && !upper_mesh.empty())
|
||||
remap_and_set_painting(upper->volumes.back(), upper_mesh.its, *saved_painting);
|
||||
if (!lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, upper, volume, cut_matrix, "_B");
|
||||
if (saved_painting)
|
||||
remap_and_set_painting(upper->volumes.back(), lower_mesh.its, *saved_painting);
|
||||
upper->volumes.back()->cut_info.is_from_upper = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper))
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepUpper)) {
|
||||
add_cut_volume(upper_mesh, upper, volume, cut_matrix);
|
||||
if (saved_painting && !upper_mesh.empty())
|
||||
remap_and_set_painting(upper->volumes.back(), upper_mesh.its, *saved_painting);
|
||||
}
|
||||
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty())
|
||||
if (attributes.has(ModelObjectCutAttribute::KeepLower) && !lower_mesh.empty()) {
|
||||
add_cut_volume(lower_mesh, lower, volume, cut_matrix);
|
||||
if (saved_painting)
|
||||
remap_and_set_painting(lower->volumes.back(), lower_mesh.its, *saved_painting);
|
||||
}
|
||||
}
|
||||
|
||||
static void reset_instance_transformation(ModelObject* object, size_t src_instance_idx,
|
||||
@@ -321,6 +360,26 @@ const ModelObjectPtrs& Cut::perform_with_plane()
|
||||
const Transform3d inverse_cut_matrix = cut_transformation.get_rotation_matrix().inverse() * translation_transform(-1. * cut_transformation.get_offset());
|
||||
|
||||
for (ModelVolume* volume : mo->volumes) {
|
||||
// Save painting data before reset_extra_facets() discards it.
|
||||
// Only for model parts that will be cut (not modifiers/connectors).
|
||||
std::optional<SavedPainting> saved_painting;
|
||||
if (volume->is_model_part() && !volume->mesh().empty()) {
|
||||
SavedPainting sp;
|
||||
// Get mesh in cut space (same transform as process_volume_cut applies)
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
const auto volume_matrix = volume->get_matrix();
|
||||
mesh.transform(inverse_cut_matrix * instance_matrix * volume_matrix, true);
|
||||
mesh.transform(m_cut_matrix);
|
||||
sp.its = std::move(mesh.its);
|
||||
sp.supported = volume->supported_facets.get_data();
|
||||
sp.seam = volume->seam_facets.get_data();
|
||||
sp.mmu = volume->mmu_segmentation_facets.get_data();
|
||||
sp.fuzzy = volume->fuzzy_skin_facets.get_data();
|
||||
if (!sp.supported.bitstream.empty() || !sp.seam.bitstream.empty() ||
|
||||
!sp.mmu.bitstream.empty() || !sp.fuzzy.bitstream.empty())
|
||||
saved_painting = std::move(sp);
|
||||
}
|
||||
|
||||
volume->reset_extra_facets();
|
||||
|
||||
if (!volume->is_model_part()) {
|
||||
@@ -330,7 +389,8 @@ const ModelObjectPtrs& Cut::perform_with_plane()
|
||||
process_connector_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower, dowels);
|
||||
}
|
||||
else if (!volume->mesh().empty())
|
||||
process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower);
|
||||
process_solid_part_cut(volume, instance_matrix, m_cut_matrix, m_attributes, upper, lower,
|
||||
saved_painting ? &*saved_painting : nullptr);
|
||||
}
|
||||
|
||||
// Post-process cut parts
|
||||
|
||||
@@ -731,6 +731,7 @@ public:
|
||||
void assign(const FacetsAnnotation &rhs) { if (! this->timestamp_matches(rhs)) { m_data = rhs.m_data; this->copy_timestamp(rhs); } }
|
||||
void assign(FacetsAnnotation &&rhs) { if (! this->timestamp_matches(rhs)) { m_data = std::move(rhs.m_data); this->copy_timestamp(rhs); } }
|
||||
const TriangleSelector::TriangleSplittingData &get_data() const noexcept { return m_data; }
|
||||
void set_data(const TriangleSelector::TriangleSplittingData &data) { m_data = data; this->touch(); }
|
||||
bool set(const TriangleSelector& selector);
|
||||
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
|
||||
// BBS
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "AABBTreeIndirect.hpp"
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -168,24 +170,25 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
|
||||
this->special_side_idx = char(special_side_idx);
|
||||
}
|
||||
|
||||
// Real-time collision detection, Ericson, Chapter 3.4
|
||||
inline Vec3f barycentric(const Vec3f& pt, const Vec3f& p1, const Vec3f& p2, const Vec3f& p3)
|
||||
{
|
||||
std::array<Vec3f, 3> v = {p2 - p1, p3 - p1, pt - p1};
|
||||
float d00 = v[0].dot(v[0]);
|
||||
float d01 = v[0].dot(v[1]);
|
||||
float d11 = v[1].dot(v[1]);
|
||||
float d20 = v[2].dot(v[0]);
|
||||
float d21 = v[2].dot(v[1]);
|
||||
float denom = d00 * d11 - d01 * d01;
|
||||
|
||||
Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom);
|
||||
barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z();
|
||||
return barycentric_cords;
|
||||
}
|
||||
|
||||
inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3)
|
||||
{
|
||||
// Real-time collision detection, Ericson, Chapter 3.4
|
||||
auto barycentric = [&pt, &p1, &p2, &p3]() -> Vec3f {
|
||||
std::array<Vec3f, 3> v = {p2 - p1, p3 - p1, pt - p1};
|
||||
float d00 = v[0].dot(v[0]);
|
||||
float d01 = v[0].dot(v[1]);
|
||||
float d11 = v[1].dot(v[1]);
|
||||
float d20 = v[2].dot(v[0]);
|
||||
float d21 = v[2].dot(v[1]);
|
||||
float denom = d00 * d11 - d01 * d01;
|
||||
|
||||
Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom);
|
||||
barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z();
|
||||
return barycentric_cords;
|
||||
};
|
||||
|
||||
Vec3f barycentric_cords = barycentric();
|
||||
Vec3f barycentric_cords = barycentric(pt, p1, p2, p3);
|
||||
return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; });
|
||||
}
|
||||
|
||||
@@ -935,7 +938,7 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i32 &n
|
||||
|
||||
if (triangle_splitting)
|
||||
split_triangle(facet_idx, neighbors);
|
||||
else if (!m_triangles[facet_idx].is_split())
|
||||
if (!m_triangles[facet_idx].is_split())
|
||||
m_triangles[facet_idx].set_state(type);
|
||||
tr = &m_triangles[facet_idx]; // might have been invalidated by split_triangle().
|
||||
|
||||
@@ -2208,4 +2211,259 @@ std::vector<EnforcerBlockerType> TriangleSelector::extract_used_facet_states(con
|
||||
return out;
|
||||
}
|
||||
|
||||
inline bool segments_intersect_proj(
|
||||
const Vec3f& p1, const Vec3f& p2, const Vec3f& p3, const Vec3f& p4, const std::function<std::pair<float, float>(const Vec3f&)>& proj)
|
||||
{
|
||||
auto cross2d = [](float ax, float ay, float bx, float by) -> float { return ax * by - ay * bx; };
|
||||
auto [u1, v1] = proj(p1);
|
||||
auto [u2, v2] = proj(p2);
|
||||
auto [u3, v3] = proj(p3);
|
||||
auto [u4, v4] = proj(p4);
|
||||
float ru = u2 - u1, rv = v2 - v1;
|
||||
float su = u4 - u3, sv = v4 - v3;
|
||||
float denom = cross2d(ru, rv, su, sv);
|
||||
if (std::abs(denom) < 1e-10f)
|
||||
return false;
|
||||
float dpu = u3 - u1, dpv = v3 - v1;
|
||||
float t1 = cross2d(dpu, dpv, su, sv) / denom;
|
||||
float t2 = cross2d(dpu, dpv, ru, rv) / denom;
|
||||
return 0.f <= t1 && t1 <= 1.0f && 0.f <= t2 && t2 <= 1.f;
|
||||
};
|
||||
|
||||
class TriangleCursor : public TriangleSelector::SinglePointCursor
|
||||
{
|
||||
public:
|
||||
TriangleCursor() = delete;
|
||||
explicit TriangleCursor(const Vec3f& center_,
|
||||
const Vec3f& source_,
|
||||
float radius_world,
|
||||
const Transform3d& trafo_,
|
||||
const TriangleSelector::ClippingPlane& clipping_plane_,
|
||||
const std::array<const Vec3f, 3>& vertices)
|
||||
: TriangleSelector::SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_)
|
||||
, vertices_(vertices)
|
||||
{}
|
||||
~TriangleCursor() override = default;
|
||||
|
||||
static std::unique_ptr<Cursor> build_cursor(const TriangleSelector& source_selector, const TriangleSelector::Triangle& tri)
|
||||
{
|
||||
const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v;
|
||||
const Vec3f& pv1 = source_selector.m_vertices[tri.verts_idxs[1]].v;
|
||||
const Vec3f& pv2 = source_selector.m_vertices[tri.verts_idxs[2]].v;
|
||||
|
||||
// Calculate the centroid of the triangle
|
||||
const Vec3f center = (pv0 + pv1 + pv2) / 3.f;
|
||||
|
||||
// Calculate the norm of the plane
|
||||
const Vec3f& norm = source_selector.m_face_normals[tri.source_triangle];
|
||||
|
||||
// Calculate the min distance from the centroid to every edges
|
||||
const float radius = std::max(std::min(std::min(point_to_line_dist(center, pv0, pv1), point_to_line_dist(center, pv0, pv2)),
|
||||
point_to_line_dist(center, pv1, pv2)), 0.1f);
|
||||
|
||||
return std::make_unique<TriangleCursor>(center, center + norm, radius, Transform3d::Identity(), TriangleSelector::ClippingPlane(),
|
||||
std::array<const Vec3f, 3>{pv0, pv1, pv2});
|
||||
}
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f& point) const override
|
||||
{
|
||||
return is_point_inside_triangle(point, vertices_[0], vertices_[1], vertices_[2]);
|
||||
}
|
||||
|
||||
bool is_edge_inside_cursor(const TriangleSelector::Triangle& tr, const std::vector<TriangleSelector::Vertex>& vertices) const override
|
||||
{
|
||||
std::array<Vec3f, 3> pts_proj; // Projected point onto the plane
|
||||
std::array<float, 3> pts_dist; // Distance to the plane, positive means above, negative means below the plane
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
Vec3f p = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
p = this->trafo * p;
|
||||
|
||||
const Vec3f diff = p - center;
|
||||
pts_dist[i] = diff.dot(dir);
|
||||
pts_proj[i] = p - pts_dist[i] * dir;
|
||||
}
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const int idx_a = side;
|
||||
const int idx_b = side < 2 ? side + 1 : 0;
|
||||
|
||||
// If both ends at the same side and farther than tolerance, skip
|
||||
const float dist_a = pts_dist[idx_a];
|
||||
const float dist_b = pts_dist[idx_b];
|
||||
if ((dist_a > tolerance_ && dist_b > tolerance_) || (dist_a < -tolerance_ && dist_b < -tolerance_)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the projected line segment which has distance to the plane within the tolerance
|
||||
Vec3f pt_a = pts_proj[idx_a];
|
||||
Vec3f pt_b = pts_proj[idx_b];
|
||||
if (std::abs(dist_a) > tolerance_) {
|
||||
pt_a = (tolerance_ - dist_b) / (dist_a - dist_b) * (pts_proj[idx_a] - pts_proj[idx_b]);
|
||||
}
|
||||
if (std::abs(dist_b) > tolerance_) {
|
||||
pt_b = (tolerance_ - dist_a) / (dist_b - dist_a) * (pts_proj[idx_b] - pts_proj[idx_a]);
|
||||
}
|
||||
|
||||
// If any projected end is inside the triangle, then is in
|
||||
if (is_point_inside_triangle(pt_a, vertices_[0], vertices_[1], vertices_[2]) ||
|
||||
is_point_inside_triangle(pt_b, vertices_[0], vertices_[1], vertices_[2])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise see if the segment (pt_a, pt_b) intersects the triangle
|
||||
{
|
||||
const Vec3f uvw_a = barycentric(pt_a, vertices_[0], vertices_[1], vertices_[2]);
|
||||
const Vec3f uvw_b = barycentric(pt_b, vertices_[0], vertices_[1], vertices_[2]);
|
||||
auto proj = [&](const Vec3f& p) -> std::pair<float, float> { return {p(0), p(1)}; };
|
||||
const Vec3f uvw_0 = {1.f,0.f,0.f};
|
||||
const Vec3f uvw_1 = {0.f,1.f,0.f};
|
||||
const Vec3f uvw_2 = {0.f,0.f,1.f};
|
||||
|
||||
if (segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_1, proj)||
|
||||
segments_intersect_proj(uvw_a, uvw_b, uvw_0, uvw_2, proj)||
|
||||
segments_intersect_proj(uvw_a, uvw_b, uvw_1, uvw_2, proj)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f>& face_normals) const override
|
||||
{
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::array<const Vec3f, 3> vertices_;
|
||||
const float tolerance_ = EPSILON;
|
||||
const float tolerance_sqr_ = tolerance_ * tolerance_;
|
||||
|
||||
static float point_to_line_dist(const Vec3f& p, const Vec3f& a, const Vec3f& b)
|
||||
{
|
||||
const Eigen::ParametrizedLine<float, 3> line = Eigen::ParametrizedLine<float, 3>::Through(a, b);
|
||||
return line.distance(p);
|
||||
}
|
||||
};
|
||||
|
||||
// Remap painting data from source mesh to target mesh using spatial mapping.
|
||||
TriangleSelector::TriangleSplittingData TriangleSelector::remap_painting(
|
||||
const indexed_triangle_set& source_its,
|
||||
const TriangleSplittingData& source_painting,
|
||||
const indexed_triangle_set& target_its)
|
||||
{
|
||||
TriangleSelector::TriangleSplittingData result;
|
||||
if (source_painting.bitstream.empty())
|
||||
return result;
|
||||
|
||||
// 1. Deserialize source painting
|
||||
TriangleMesh source_mesh(source_its);
|
||||
TriangleSelector source_selector(source_mesh);
|
||||
source_selector.deserialize(source_painting, false);
|
||||
|
||||
// 2. Extract painted geometry
|
||||
std::vector<std::reference_wrapper<const Triangle>> painted_triangles;
|
||||
painted_triangles.reserve(source_selector.m_triangles.size());
|
||||
for (const Triangle& tr : source_selector.m_triangles) {
|
||||
if (tr.valid() && !tr.is_split() && tr.get_state() != EnforcerBlockerType::NONE) {
|
||||
painted_triangles.push_back(std::ref(tr));
|
||||
}
|
||||
}
|
||||
|
||||
if (painted_triangles.empty())
|
||||
return result;
|
||||
|
||||
// 3. Build AABB tree of target mesh so we could find nearest face quickly
|
||||
AABBTreeIndirect::Tree3f target_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(target_its.vertices, target_its.indices);
|
||||
|
||||
// Helper: check overlap between a paint triangle and a target triangle.
|
||||
// Uses 3D barycentric point-in-triangle tests and dominant-axis 2D projection
|
||||
// for edge-edge intersection to handle all triangle orientations correctly.
|
||||
auto check_overlap = [&](const Vec3f& pa, const Vec3f& pb, const Vec3f& pc,
|
||||
const Vec3f& ta, const Vec3f& tb, const Vec3f& tc) -> bool {
|
||||
// Check if any target vertex is inside the paint triangle
|
||||
if (is_point_inside_triangle(ta, pa, pb, pc)) return true;
|
||||
if (is_point_inside_triangle(tb, pa, pb, pc)) return true;
|
||||
if (is_point_inside_triangle(tc, pa, pb, pc)) return true;
|
||||
|
||||
// Check if any paint vertex is inside the target triangle
|
||||
if (is_point_inside_triangle(pa, ta, tb, tc)) return true;
|
||||
if (is_point_inside_triangle(pb, ta, tb, tc)) return true;
|
||||
if (is_point_inside_triangle(pc, ta, tb, tc)) return true;
|
||||
|
||||
// Check edge-edge intersections using dominant-axis 2D projection.
|
||||
// Project onto the plane (XY, XZ, or YZ) where the triangles have the
|
||||
// most area, avoiding degenerate projections for vertical/angled surfaces.
|
||||
Vec3f n1 = (pb - pa).cross(pc - pa);
|
||||
Vec3f n2 = (tb - ta).cross(tc - ta);
|
||||
Vec3f n_abs = (n1.cwiseAbs() + n2.cwiseAbs());
|
||||
int axis = (n_abs.x() >= n_abs.y() && n_abs.x() >= n_abs.z()) ? 0
|
||||
: (n_abs.y() >= n_abs.z()) ? 1 : 2;
|
||||
int u_axis = (axis + 1) % 3;
|
||||
int v_axis = (axis + 2) % 3;
|
||||
|
||||
auto proj = [&](const Vec3f& p) -> std::pair<float, float> {
|
||||
return { p(u_axis), p(v_axis) };
|
||||
};
|
||||
|
||||
if (segments_intersect_proj(pa, pb, ta, tb, proj)) return true;
|
||||
if (segments_intersect_proj(pa, pb, tb, tc, proj)) return true;
|
||||
if (segments_intersect_proj(pa, pb, tc, ta, proj)) return true;
|
||||
if (segments_intersect_proj(pb, pc, ta, tb, proj)) return true;
|
||||
if (segments_intersect_proj(pb, pc, tb, tc, proj)) return true;
|
||||
if (segments_intersect_proj(pb, pc, tc, ta, proj)) return true;
|
||||
if (segments_intersect_proj(pc, pa, ta, tb, proj)) return true;
|
||||
if (segments_intersect_proj(pc, pa, tb, tc, proj)) return true;
|
||||
if (segments_intersect_proj(pc, pa, tc, ta, proj)) return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// 4. For each painted face, we find the nearest target face, and apply the TriangleCursor to paint it
|
||||
TriangleMesh target_mesh(target_its);
|
||||
TriangleSelector target_selector(target_mesh);
|
||||
const auto facet_angle_limit = cos(Geometry::deg2rad(5));
|
||||
auto check_normal = [&source_selector, &target_selector, facet_angle_limit](int src_idx, int dst_idx) -> bool {
|
||||
const Vec3f& norm_a = source_selector.m_face_normals[src_idx];
|
||||
const Vec3f& norm_b = target_selector.m_face_normals[dst_idx];
|
||||
const float a = norm_a.dot(norm_b);
|
||||
|
||||
return std::clamp(a, 0.f, 1.f) >= facet_angle_limit;
|
||||
};
|
||||
for (auto tri_ref : painted_triangles) {
|
||||
const Triangle& tri = tri_ref.get();
|
||||
const Vec3f& pv0 = source_selector.m_vertices[tri.verts_idxs[0]].v;
|
||||
const Vec3f& pv1 = source_selector.m_vertices[tri.verts_idxs[1]].v;
|
||||
const Vec3f& pv2 = source_selector.m_vertices[tri.verts_idxs[2]].v;
|
||||
|
||||
// Find ALL target faces whose bounding boxes overlap with the paint
|
||||
// triangle's bounding box, not just the nearest one.
|
||||
Eigen::AlignedBox3f pt_bbox;
|
||||
pt_bbox.extend(pv0);
|
||||
pt_bbox.extend(pv1);
|
||||
pt_bbox.extend(pv2);
|
||||
|
||||
AABBTreeIndirect::traverse(target_tree, AABBTreeIndirect::intersecting(pt_bbox), [&](const AABBTreeIndirect::Tree3f::Node& node) -> bool {
|
||||
size_t face_idx = node.idx;
|
||||
if (face_idx >= target_its.indices.size())
|
||||
return true;
|
||||
|
||||
const Vec3i32& face = target_its.indices[face_idx];
|
||||
const Vec3f& ta = target_its.vertices[face(0)];
|
||||
const Vec3f& tb = target_its.vertices[face(1)];
|
||||
const Vec3f& tc = target_its.vertices[face(2)];
|
||||
|
||||
if (check_normal(tri.source_triangle, face_idx) && check_overlap(pv0, pv1, pv2, ta, tb, tc)) {
|
||||
// Paint this face
|
||||
target_selector.select_patch(face_idx, TriangleCursor::build_cursor(source_selector, tri), tri.get_state(),
|
||||
Transform3d::Identity(), true);
|
||||
}
|
||||
return true; // continue traversal
|
||||
});
|
||||
}
|
||||
|
||||
return target_selector.serialize();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -372,6 +372,13 @@ public:
|
||||
// The operation may merge split triangles if they are being assigned the same color.
|
||||
void seed_fill_apply_on_triangles(EnforcerBlockerType new_state);
|
||||
|
||||
// Remap painting data from source mesh to target mesh using spatial mapping.
|
||||
// Both meshes must be in the same coordinate space.
|
||||
static TriangleSplittingData remap_painting(
|
||||
const indexed_triangle_set& source_its,
|
||||
const TriangleSplittingData& source_painting,
|
||||
const indexed_triangle_set& target_its);
|
||||
|
||||
protected:
|
||||
// Triangle and info about how it's split.
|
||||
class Triangle {
|
||||
@@ -521,11 +528,10 @@ private:
|
||||
|
||||
int m_free_triangles_head { -1 };
|
||||
int m_free_vertices_head { -1 };
|
||||
|
||||
friend class TriangleCursor;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // libslic3r_TriangleSelector_hpp_
|
||||
|
||||
Reference in New Issue
Block a user