From 22f93a34a8c3ac36e17d271cb65eeb975af6cd60 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Mon, 9 Nov 2020 08:16:55 +0100 Subject: [PATCH] Fix horrible complexity of custom seam lookup (#5067) - polygons are offset individually - custom areas are kept separately for each PrintObject - AABB tree is used to get logN lookup complexity --- src/libslic3r/AABBTreeIndirect.hpp | 30 +++++ src/libslic3r/GCode/SeamPlacer.cpp | 169 ++++++++++++++++++++++------- src/libslic3r/GCode/SeamPlacer.hpp | 37 +++++-- src/libslic3r/PrintObject.cpp | 1 - 4 files changed, 183 insertions(+), 54 deletions(-) diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp index 964133faa..19fbf1378 100644 --- a/src/libslic3r/AABBTreeIndirect.hpp +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -726,6 +726,36 @@ inline bool is_any_triangle_in_radius( return hit_point.allFinite(); } + +// Traverse the tree and return the index of an entity whose bounding box +// contains a given point. Returns size_t(-1) when the point is outside. +template +size_t get_candidate_idx(const TreeType& tree, const VectorType& v) +{ + if (tree.empty() || ! tree.node(0).bbox.contains(v)) + return size_t(-1); + + size_t node_idx = 0; + while (true) { + decltype(tree.node(node_idx)) node = tree.node(node_idx); + static_assert(std::is_reference::value, + "Nodes shall be addressed by reference."); + assert(node.is_valid()); + assert(node.bbox.contains(v)); + + if (! node.is_leaf()) { + if (tree.left_child(node_idx).bbox.contains(v)) + node_idx = tree.left_child_idx(node_idx); + else if (tree.right_child(node_idx).bbox.contains(v)) + node_idx = tree.right_child_idx(node_idx); + else + return size_t(-1); + } else + return node.idx; + } +} + + } // namespace AABBTreeIndirect } // namespace Slic3r diff --git a/src/libslic3r/GCode/SeamPlacer.cpp b/src/libslic3r/GCode/SeamPlacer.cpp index db31f8f67..ff01621b8 100644 --- a/src/libslic3r/GCode/SeamPlacer.cpp +++ b/src/libslic3r/GCode/SeamPlacer.cpp @@ -191,19 +191,93 @@ void SeamPlacer::init(const Print& print) { m_enforcers.clear(); m_blockers.clear(); - //m_last_seam_position.clear(); m_seam_history.clear(); + m_po_list.clear(); - for (const PrintObject* po : print.objects()) { - po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, m_enforcers); - po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, m_blockers); - } - const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; - float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); - for (ExPolygons& explgs : m_enforcers) - explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); - for (ExPolygons& explgs : m_blockers) - explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); + const std::vector& nozzle_dmrs = print.config().nozzle_diameter.values; + float max_nozzle_dmr = *std::max_element(nozzle_dmrs.begin(), nozzle_dmrs.end()); + + + std::vector temp_enf; + std::vector temp_blk; + + for (const PrintObject* po : print.objects()) { + temp_enf.clear(); + temp_blk.clear(); + po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, temp_enf); + po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, temp_blk); + + // Offset the triangles out slightly. + for (auto* custom_per_object : {&temp_enf, &temp_blk}) + for (ExPolygons& explgs : *custom_per_object) + explgs = Slic3r::offset_ex(explgs, scale_(max_nozzle_dmr)); + +// FIXME: Offsetting should be done somehow cheaper, but following does not work +// for (auto* custom_per_object : {&temp_enf, &temp_blk}) { +// for (ExPolygons& plgs : *custom_per_object) { +// for (ExPolygon& plg : plgs) { +// auto out = Slic3r::offset_ex(plg, scale_(max_nozzle_dmr)); +// plg = out.empty() ? ExPolygon() : out.front(); +// assert(out.empty() || out.size() == 1); +// } +// } +// } + + + + // Remember this PrintObject and initialize a store of enforcers and blockers for it. + m_po_list.push_back(po); + size_t po_idx = m_po_list.size() - 1; + m_enforcers.emplace_back(std::vector(temp_enf.size())); + m_blockers.emplace_back(std::vector(temp_blk.size())); + + // A helper class to store data to build the AABB tree from. + class CustomTriangleRef { + public: + CustomTriangleRef(size_t idx, + Point&& centroid, + BoundingBox&& bb) + : m_idx{idx}, m_centroid{centroid}, + m_bbox{AlignedBoxType(bb.min, bb.max)} + {} + size_t idx() const { return m_idx; } + const Point& centroid() const { return m_centroid; } + const TreeType::BoundingBox& bbox() const { return m_bbox; } + + private: + size_t m_idx; + Point m_centroid; + AlignedBoxType m_bbox; + }; + + // A lambda to extract the ExPolygons and save them into the member AABB tree. + // Will be called for enforcers and blockers separately. + auto add_custom = [](std::vector& src, std::vector& dest) { + // Go layer by layer, and append all the ExPolygons into the AABB tree. + size_t layer_idx = 0; + for (ExPolygons& expolys_on_layer : src) { + CustomTrianglesPerLayer& layer_data = dest[layer_idx]; + std::vector triangles_data; + layer_data.polys.reserve(expolys_on_layer.size()); + triangles_data.reserve(expolys_on_layer.size()); + + for (ExPolygon& expoly : expolys_on_layer) { + if (expoly.empty()) + continue; + layer_data.polys.emplace_back(std::move(expoly)); + triangles_data.emplace_back(layer_data.polys.size() - 1, + layer_data.polys.back().centroid(), + layer_data.polys.back().bounding_box()); + } + // All polygons are saved, build the AABB tree for them. + layer_data.tree.build(std::move(triangles_data)); + ++layer_idx; + } + }; + + add_custom(temp_enf, m_enforcers.at(po_idx)); + add_custom(temp_blk, m_blockers.at(po_idx)); + } } @@ -216,7 +290,9 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit BoundingBox polygon_bb = polygon.bounding_box(); const coord_t nozzle_r = coord_t(scale_(0.5 * nozzle_dmr) + 0.5); - if (this->is_custom_seam_on_layer(layer_idx)) { + size_t po_idx = std::find(m_po_list.begin(), m_po_list.end(), po) - m_po_list.begin(); + + if (this->is_custom_seam_on_layer(layer_idx, po_idx)) { // Seam enf/blockers can begin and end in between the original vertices. // Let add extra points in between and update the leghths. polygon.densify(MINIMAL_POLYGON_SIDE); @@ -229,11 +305,10 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit if (seam_position == spAligned) { // Seam is aligned to the seam at the preceding layer. if (po != nullptr) { - std::optional pos = m_seam_history.get_last_seam(po, layer_idx, polygon_bb); + std::optional pos = m_seam_history.get_last_seam(m_po_list[po_idx], layer_idx, polygon_bb); if (pos.has_value()) { - //last_pos = m_last_seam_position[po]; last_pos = *pos; - last_pos_weight = is_custom_enforcer_on_layer(layer_idx) ? 0.f : 1.f; + last_pos_weight = is_custom_enforcer_on_layer(layer_idx, po_idx) ? 0.f : 1.f; } } } @@ -313,12 +388,12 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit // Custom seam. Huge (negative) constant penalty is applied inside // blockers (enforcers) to rule out points that should not win. - this->apply_custom_seam(polygon, penalties, lengths, layer_idx, seam_position); + this->apply_custom_seam(polygon, po_idx, penalties, lengths, layer_idx, seam_position); // Find a point with a minimum penalty. size_t idx_min = std::min_element(penalties.begin(), penalties.end()) - penalties.begin(); - if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx)) { + if (seam_position != spAligned || ! is_custom_enforcer_on_layer(layer_idx, po_idx)) { // Very likely the weight of idx_min is very close to the weight of last_pos_proj_idx. // In that case use last_pos_proj_idx instead. float penalty_aligned = penalties[last_pos_proj_idx]; @@ -368,15 +443,15 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit // The other loops will get a seam close to the random point chosen // on the innermost contour. //FIXME This works correctly for inner contours first only. - last_pos = this->get_random_seam(layer_idx, polygon); + last_pos = this->get_random_seam(layer_idx, polygon, po_idx); } - if (loop.role() == erExternalPerimeter && is_custom_seam_on_layer(layer_idx)) { + if (loop.role() == erExternalPerimeter && is_custom_seam_on_layer(layer_idx, po_idx)) { // There is a possibility that the loop will be influenced by custom // seam enforcer/blocker. In this case do not inherit the seam // from internal loops (which may conflict with the custom selection // and generate another random one. bool saw_custom = false; - Point candidate = this->get_random_seam(layer_idx, polygon, &saw_custom); + Point candidate = this->get_random_seam(layer_idx, polygon, po_idx, &saw_custom); if (saw_custom) last_pos = candidate; } @@ -385,7 +460,7 @@ Point SeamPlacer::get_seam(const size_t layer_idx, const SeamPosition seam_posit } -Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, +Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, size_t po_idx, bool* saw_custom) const { // Parametrize the polygon by its length. @@ -394,7 +469,7 @@ Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, // Which of the points are inside enforcers/blockers? std::vector enforcers_idxs; std::vector blockers_idxs; - this->get_enforcers_and_blockers(layer_idx, polygon, enforcers_idxs, blockers_idxs); + this->get_enforcers_and_blockers(layer_idx, polygon, po_idx, enforcers_idxs, blockers_idxs); bool has_enforcers = ! enforcers_idxs.empty(); bool has_blockers = ! blockers_idxs.empty(); @@ -444,32 +519,44 @@ Point SeamPlacer::get_random_seam(size_t layer_idx, const Polygon& polygon, void SeamPlacer::get_enforcers_and_blockers(size_t layer_id, const Polygon& polygon, + size_t po_idx, std::vector& enforcers_idxs, std::vector& blockers_idxs) const { enforcers_idxs.clear(); blockers_idxs.clear(); - // FIXME: This is quadratic and it should be improved, maybe by building - // an AABB tree (or at least utilize bounding boxes). - for (size_t i=0; i bool { + assert(! custom_data.polys.empty()); + // Now ask the AABB tree which polygon we should check and check it. + size_t candidate = AABBTreeIndirect::get_candidate_idx(custom_data.tree, pt); + if (candidate != size_t(-1) + && custom_data.polys[candidate].contains(pt)) + return true; + return false; + }; - if (! m_enforcers.empty()) { - assert(layer_id < m_enforcers.size()); - for (const ExPolygon& explg : m_enforcers[layer_id]) { - if (explg.contains(polygon.points[i])) - enforcers_idxs.push_back(i); - } - } - - if (! m_blockers.empty()) { - assert(layer_id < m_blockers.size()); - for (const ExPolygon& explg : m_blockers[layer_id]) { - if (explg.contains(polygon.points[i])) - blockers_idxs.push_back(i); + if (! m_enforcers[po_idx].empty()) { + const CustomTrianglesPerLayer& enforcers = m_enforcers[po_idx][layer_id]; + if (! enforcers.polys.empty()) { + for (size_t i=0; i find_enforcer_centers(const Polygon& polygon, -void SeamPlacer::apply_custom_seam(const Polygon& polygon, +void SeamPlacer::apply_custom_seam(const Polygon& polygon, size_t po_idx, std::vector& penalties, const std::vector& lengths, int layer_id, SeamPosition seam_position) const { - if (! is_custom_seam_on_layer(layer_id)) + if (! is_custom_seam_on_layer(layer_id, po_idx)) return; std::vector enforcers_idxs; std::vector blockers_idxs; - this->get_enforcers_and_blockers(layer_id, polygon, enforcers_idxs, blockers_idxs); + this->get_enforcers_and_blockers(layer_id, polygon, po_idx, enforcers_idxs, blockers_idxs); for (size_t i : enforcers_idxs) { assert(i < penalties.size()); diff --git a/src/libslic3r/GCode/SeamPlacer.hpp b/src/libslic3r/GCode/SeamPlacer.hpp index e603b7d57..28ab91313 100644 --- a/src/libslic3r/GCode/SeamPlacer.hpp +++ b/src/libslic3r/GCode/SeamPlacer.hpp @@ -3,9 +3,10 @@ #include -#include "libslic3r/ExPolygon.hpp" +#include "libslic3r/Polygon.hpp" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/AABBTreeIndirect.hpp" namespace Slic3r { @@ -44,9 +45,20 @@ public: coordf_t nozzle_diameter, const PrintObject* po, bool was_clockwise, const EdgeGrid::Grid* lower_layer_edge_grid); + using TreeType = AABBTreeIndirect::Tree<2, coord_t>; + using AlignedBoxType = Eigen::AlignedBox; + private: - std::vector m_enforcers; - std::vector m_blockers; + + struct CustomTrianglesPerLayer { + Polygons polys; + TreeType tree; + }; + + + std::vector> m_enforcers; + std::vector> m_blockers; + std::vector m_po_list; //std::map m_last_seam_position; SeamHistory m_seam_history; @@ -54,32 +66,33 @@ private: // Get indices of points inside enforcers and blockers. void get_enforcers_and_blockers(size_t layer_id, const Polygon& polygon, + size_t po_id, std::vector& enforcers_idxs, std::vector& blockers_idxs) const; // Apply penalties to points inside enforcers/blockers. - void apply_custom_seam(const Polygon& polygon, + void apply_custom_seam(const Polygon& polygon, size_t po_id, std::vector& penalties, const std::vector& lengths, int layer_id, SeamPosition seam_position) const; // Return random point of a polygon. The distribution will be uniform // along the contour and account for enforcers and blockers. - Point get_random_seam(size_t layer_idx, const Polygon& polygon, + Point get_random_seam(size_t layer_idx, const Polygon& polygon, size_t po_id, bool* saw_custom = nullptr) const; // Is there any enforcer/blocker on this layer? - bool is_custom_seam_on_layer(size_t layer_id) const { - return is_custom_enforcer_on_layer(layer_id) - || is_custom_blocker_on_layer(layer_id); + bool is_custom_seam_on_layer(size_t layer_id, size_t po_idx) const { + return is_custom_enforcer_on_layer(layer_id, po_idx) + || is_custom_blocker_on_layer(layer_id, po_idx); } - bool is_custom_enforcer_on_layer(size_t layer_id) const { - return (! m_enforcers.empty() && ! m_enforcers[layer_id].empty()); + bool is_custom_enforcer_on_layer(size_t layer_id, size_t po_idx) const { + return (! m_enforcers.at(po_idx).empty() && ! m_enforcers.at(po_idx)[layer_id].polys.empty()); } - bool is_custom_blocker_on_layer(size_t layer_id) const { - return (! m_blockers.empty() && ! m_blockers[layer_id].empty()); + bool is_custom_blocker_on_layer(size_t layer_id, size_t po_idx) const { + return (! m_blockers.at(po_idx).empty() && ! m_blockers.at(po_idx)[layer_id].polys.empty()); } }; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c70542a26..51b3128ba 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -11,7 +11,6 @@ #include "Slicing.hpp" #include "Tesselate.hpp" #include "Utils.hpp" -#include "AABBTreeIndirect.hpp" #include "Fill/FillAdaptive.hpp" #include "Format/STL.hpp"