Refactored Arachne to use ankerl::unordered_dense hash tables

instead of std::unordered_set/map
The code is now generic enough to enable experiments with various
hash maps in the future.
This commit is contained in:
Vojtech Bubnik 2023-04-21 15:41:00 +02:00
parent 29719a7ab9
commit 0d1522791d
13 changed files with 49 additions and 39 deletions

View File

@ -5,7 +5,6 @@
#include <stack> #include <stack>
#include <functional> #include <functional>
#include <unordered_set>
#include <sstream> #include <sstream>
#include <queue> #include <queue>
#include <functional> #include <functional>
@ -522,9 +521,11 @@ static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph)
return false; return false;
} }
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph, using PointMap = SkeletalTrapezoidation::PointMap;
const double fix_angle,
const std::unordered_map<Point, Point, PointHash> &vertex_mapping) inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
const double fix_angle,
const PointMap &vertex_mapping)
{ {
for (STHalfEdgeNode &node : graph.nodes) { for (STHalfEdgeNode &node : graph.nodes) {
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction. // If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
@ -588,7 +589,7 @@ VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::Voronoi
return VoronoiDiagramStatus::NO_ISSUE_DETECTED; return VoronoiDiagramStatus::NO_ISSUE_DETECTED;
} }
inline static std::pair<std::unordered_map<Point, Point, PointHash>, double> try_to_fix_degenerated_voronoi_diagram_by_rotation( inline static std::pair<PointMap, double> try_to_fix_degenerated_voronoi_diagram_by_rotation(
Geometry::VoronoiDiagram &voronoi_diagram, Geometry::VoronoiDiagram &voronoi_diagram,
const Polygons &polys, const Polygons &polys,
Polygons &polys_rotated, Polygons &polys_rotated,
@ -597,7 +598,7 @@ inline static std::pair<std::unordered_map<Point, Point, PointHash>, double> try
{ {
const Polygons polys_rotated_original = polys_rotated; const Polygons polys_rotated_original = polys_rotated;
double fixed_by_angle = fix_angles.front(); double fixed_by_angle = fix_angles.front();
std::unordered_map<Point, Point, PointHash> vertex_mapping; PointMap vertex_mapping;
for (const double &fix_angle : fix_angles) { for (const double &fix_angle : fix_angles) {
vertex_mapping.clear(); vertex_mapping.clear();
@ -685,7 +686,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11}; const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
double fixed_by_angle = fix_angles.front(); double fixed_by_angle = fix_angles.front();
std::unordered_map<Point, Point, PointHash> vertex_mapping; PointMap vertex_mapping;
// polys_copy is referenced through items stored in the std::vector segments. // polys_copy is referenced through items stored in the std::vector segments.
Polygons polys_copy = polys; Polygons polys_copy = polys;
if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) { if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
@ -813,9 +814,11 @@ process_voronoi_diagram:
edge.from->incident_edge = &edge; edge.from->incident_edge = &edge;
} }
using NodeSet = SkeletalTrapezoidation::NodeSet;
void SkeletalTrapezoidation::separatePointyQuadEndNodes() void SkeletalTrapezoidation::separatePointyQuadEndNodes()
{ {
std::unordered_set<node_t*> visited_nodes; NodeSet visited_nodes;
for (edge_t& edge : graph.edges) for (edge_t& edge : graph.edges)
{ {
if (edge.prev) if (edge.prev)
@ -2285,16 +2288,18 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c
void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_junctions) void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_junctions)
{ {
std::unordered_set<edge_t*> unprocessed_quad_starts(graph.edges.size() * 5 / 2); using EdgeSet = ankerl::unordered_dense::set<edge_t*>;
EdgeSet unprocessed_quad_starts(graph.edges.size() * 5 / 2);
for (edge_t& edge : graph.edges) for (edge_t& edge : graph.edges)
{ {
if (!edge.prev) if (!edge.prev)
{ {
unprocessed_quad_starts.insert(&edge); unprocessed_quad_starts.emplace(&edge);
} }
} }
std::unordered_set<edge_t*> passed_odd_edges; EdgeSet passed_odd_edges;
while (!unprocessed_quad_starts.empty()) while (!unprocessed_quad_starts.empty())
{ {

View File

@ -7,8 +7,10 @@
#include <boost/polygon/voronoi.hpp> #include <boost/polygon/voronoi.hpp>
#include <memory> // smart pointers #include <memory> // smart pointers
#include <unordered_map>
#include <utility> // pair #include <utility> // pair
#include <ankerl/unordered_dense.h>
#include <Arachne/utils/VoronoiUtils.hpp> #include <Arachne/utils/VoronoiUtils.hpp>
#include "utils/HalfEdgeGraph.hpp" #include "utils/HalfEdgeGraph.hpp"
@ -80,7 +82,9 @@ class SkeletalTrapezoidation
const BeadingStrategy& beading_strategy; const BeadingStrategy& beading_strategy;
public: public:
using Segment = PolygonsSegmentIndex; using Segment = PolygonsSegmentIndex;
using PointMap = ankerl::unordered_dense::map<Point, Point, PointHash>;
using NodeSet = ankerl::unordered_dense::set<node_t*>;
/*! /*!
* Construct a new trapezoidation problem to solve. * Construct a new trapezoidation problem to solve.
@ -164,8 +168,8 @@ protected:
* mapping each voronoi VD edge to the corresponding halfedge HE edge * mapping each voronoi VD edge to the corresponding halfedge HE edge
* In case the result segment is discretized, we map the VD edge to the *last* HE edge * In case the result segment is discretized, we map the VD edge to the *last* HE edge
*/ */
std::unordered_map<vd_t::edge_type*, edge_t*> vd_edge_to_he_edge; ankerl::unordered_dense::map<vd_t::edge_type*, edge_t*> vd_edge_to_he_edge;
std::unordered_map<vd_t::vertex_type*, node_t*> vd_node_to_he_node; ankerl::unordered_dense::map<vd_t::vertex_type*, node_t*> vd_node_to_he_node;
node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet. node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
/*! /*!

View File

@ -2,7 +2,8 @@
//CuraEngine is released under the terms of the AGPLv3 or higher. //CuraEngine is released under the terms of the AGPLv3 or higher.
#include "SkeletalTrapezoidationGraph.hpp" #include "SkeletalTrapezoidationGraph.hpp"
#include <unordered_map>
#include <ankerl/unordered_dense.h>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
@ -180,8 +181,8 @@ bool STHalfEdgeNode::isLocalMaximum(bool strict) const
void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
{ {
std::unordered_map<edge_t*, std::list<edge_t>::iterator> edge_locator; ankerl::unordered_dense::map<edge_t*, Edges::iterator> edge_locator;
std::unordered_map<node_t*, std::list<node_t>::iterator> node_locator; ankerl::unordered_dense::map<node_t*, Nodes::iterator> node_locator;
for (auto edge_it = edges.begin(); edge_it != edges.end(); ++edge_it) for (auto edge_it = edges.begin(); edge_it != edges.end(); ++edge_it)
{ {
@ -193,7 +194,7 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist)
node_locator.emplace(&*node_it, node_it); node_locator.emplace(&*node_it, node_it);
} }
auto safelyRemoveEdge = [this, &edge_locator](edge_t* to_be_removed, std::list<edge_t>::iterator& current_edge_it, bool& edge_it_is_updated) auto safelyRemoveEdge = [this, &edge_locator](edge_t* to_be_removed, Edges::iterator& current_edge_it, bool& edge_it_is_updated)
{ {
if (current_edge_it != edges.end() if (current_edge_it != edges.end()
&& to_be_removed == &*current_edge_it) && to_be_removed == &*current_edge_it)

View File

@ -2,7 +2,6 @@
// CuraEngine is released under the terms of the AGPLv3 or higher. // CuraEngine is released under the terms of the AGPLv3 or higher.
#include <algorithm> //For std::partition_copy and std::min_element. #include <algorithm> //For std::partition_copy and std::min_element.
#include <unordered_set>
#include "WallToolPaths.hpp" #include "WallToolPaths.hpp"
@ -767,9 +766,9 @@ bool WallToolPaths::removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpa
* *
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
*/ */
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> WallToolPaths::getRegionOrder(const std::vector<ExtrusionLine *> &input, const bool outer_to_inner) WallToolPaths::ExtrusionLineSet WallToolPaths::getRegionOrder(const std::vector<ExtrusionLine *> &input, const bool outer_to_inner)
{ {
std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> order_requirements; ExtrusionLineSet order_requirements;
// We build a grid where we map toolpath vertex locations to toolpaths, // We build a grid where we map toolpath vertex locations to toolpaths,
// so that we can easily find which two toolpaths are next to each other, // so that we can easily find which two toolpaths are next to each other,

View File

@ -5,7 +5,8 @@
#define CURAENGINE_WALLTOOLPATHS_H #define CURAENGINE_WALLTOOLPATHS_H
#include <memory> #include <memory>
#include <unordered_set>
#include <ankerl/unordered_dense.h>
#include "BeadingStrategy/BeadingStrategyFactory.hpp" #include "BeadingStrategy/BeadingStrategyFactory.hpp"
#include "utils/ExtrusionLine.hpp" #include "utils/ExtrusionLine.hpp"
@ -73,6 +74,7 @@ public:
*/ */
static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths); static bool removeEmptyToolPaths(std::vector<VariableWidthLines> &toolpaths);
using ExtrusionLineSet = ankerl::unordered_dense::set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>>;
/*! /*!
* Get the order constraints of the insets when printing walls per region / hole. * Get the order constraints of the insets when printing walls per region / hole.
* Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right. * Each returned pair consists of adjacent wall lines where the left has an inset_idx one lower than the right.
@ -81,7 +83,7 @@ public:
* *
* \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one. * \param outer_to_inner Whether the wall polygons with a lower inset_idx should go before those with a higher one.
*/ */
static std::unordered_set<std::pair<const ExtrusionLine *, const ExtrusionLine *>, boost::hash<std::pair<const ExtrusionLine *, const ExtrusionLine *>>> getRegionOrder(const std::vector<ExtrusionLine *> &input, bool outer_to_inner); static ExtrusionLineSet getRegionOrder(const std::vector<ExtrusionLine *> &input, bool outer_to_inner);
protected: protected:
/*! /*!

View File

@ -21,8 +21,10 @@ class HalfEdgeGraph
public: public:
using edge_t = derived_edge_t; using edge_t = derived_edge_t;
using node_t = derived_node_t; using node_t = derived_node_t;
std::list<edge_t> edges; using Edges = std::list<edge_t>;
std::list<node_t> nodes; using Nodes = std::list<node_t>;
Edges edges;
Nodes nodes;
}; };
} // namespace Slic3r::Arachne } // namespace Slic3r::Arachne

View File

@ -7,7 +7,6 @@
#include "SparsePointGrid.hpp" #include "SparsePointGrid.hpp"
#include "PolygonsPointIndex.hpp" #include "PolygonsPointIndex.hpp"
#include "../../Polygon.hpp" #include "../../Polygon.hpp"
#include <unordered_set>
#include <cassert> #include <cassert>
namespace Slic3r::Arachne namespace Slic3r::Arachne

View File

@ -6,7 +6,6 @@
#define UTILS_SPARSE_GRID_H #define UTILS_SPARSE_GRID_H
#include <cassert> #include <cassert>
#include <unordered_map>
#include <vector> #include <vector>
#include <functional> #include <functional>

View File

@ -6,7 +6,6 @@
#define UTILS_SPARSE_LINE_GRID_H #define UTILS_SPARSE_LINE_GRID_H
#include <cassert> #include <cassert>
#include <unordered_map>
#include <vector> #include <vector>
#include <functional> #include <functional>

View File

@ -6,7 +6,6 @@
#define UTILS_SPARSE_POINT_GRID_H #define UTILS_SPARSE_POINT_GRID_H
#include <cassert> #include <cassert>
#include <unordered_map>
#include <vector> #include <vector>
#include "SparseGrid.hpp" #include "SparseGrid.hpp"

View File

@ -7,7 +7,6 @@
#include "../../Point.hpp" #include "../../Point.hpp"
#include <cassert> #include <cassert>
#include <unordered_map>
#include <vector> #include <vector>
#include <functional> #include <functional>

View File

@ -39,11 +39,11 @@
#include <ostream> #include <ostream>
#include <stack> #include <stack>
#include <string> #include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include <ankerl/unordered_dense.h>
// #define ARACHNE_DEBUG // #define ARACHNE_DEBUG
#ifdef ARACHNE_DEBUG #ifdef ARACHNE_DEBUG
@ -569,7 +569,7 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator::P
size_t occurrence = 0; size_t occurrence = 0;
bool is_overhang = false; bool is_overhang = false;
}; };
std::unordered_map<Point, PointInfo, PointHash> point_occurrence; ankerl::unordered_dense::map<Point, PointInfo, PointHash> point_occurrence;
for (const ExtrusionPath &path : paths) { for (const ExtrusionPath &path : paths) {
++point_occurrence[path.polyline.first_point()].occurrence; ++point_occurrence[path.polyline.first_point()].occurrence;
++point_occurrence[path.polyline.last_point()].occurrence; ++point_occurrence[path.polyline.last_point()].occurrence;
@ -681,7 +681,7 @@ Polylines reconnect_polylines(const Polylines &polylines, double limit_distance)
if (polylines.empty()) if (polylines.empty())
return polylines; return polylines;
std::unordered_map<size_t, Polyline> connected; ankerl::unordered_dense::map<size_t, Polyline> connected;
connected.reserve(polylines.size()); connected.reserve(polylines.size());
for (size_t i = 0; i < polylines.size(); i++) { for (size_t i = 0; i < polylines.size(); i++) {
if (!polylines[i].empty()) { if (!polylines[i].empty()) {
@ -731,7 +731,7 @@ ExtrusionPaths sort_extra_perimeters(ExtrusionPaths extra_perims, int index_of_f
{ {
if (extra_perims.empty()) return {}; if (extra_perims.empty()) return {};
std::vector<std::unordered_set<size_t>> dependencies(extra_perims.size()); std::vector<ankerl::unordered_dense::set<size_t>> dependencies(extra_perims.size());
for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) { for (size_t path_idx = 0; path_idx < extra_perims.size(); path_idx++) {
for (size_t prev_path_idx = 0; prev_path_idx < path_idx; prev_path_idx++) { for (size_t prev_path_idx = 0; prev_path_idx < path_idx; prev_path_idx++) {
if (paths_touch(extra_perims[path_idx], extra_perims[prev_path_idx], extrusion_spacing * 1.5f)) { if (paths_touch(extra_perims[path_idx], extra_perims[prev_path_idx], extrusion_spacing * 1.5f)) {
@ -1153,11 +1153,11 @@ void PerimeterGenerator::process_arachne(
// Find topological order with constraints from extrusions_constrains. // Find topological order with constraints from extrusions_constrains.
std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion. std::vector<size_t> blocked(all_extrusions.size(), 0); // Value indicating how many extrusions it is blocking (preceding extrusions) an extrusion.
std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion. std::vector<std::vector<size_t>> blocking(all_extrusions.size()); // Each extrusion contains a vector of extrusions that are blocked by this extrusion.
std::unordered_map<const Arachne::ExtrusionLine *, size_t> map_extrusion_to_idx; ankerl::unordered_dense::map<const Arachne::ExtrusionLine *, size_t> map_extrusion_to_idx;
for (size_t idx = 0; idx < all_extrusions.size(); idx++) for (size_t idx = 0; idx < all_extrusions.size(); idx++)
map_extrusion_to_idx.emplace(all_extrusions[idx], idx); map_extrusion_to_idx.emplace(all_extrusions[idx], idx);
auto extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first); Arachne::WallToolPaths::ExtrusionLineSet extrusions_constrains = Arachne::WallToolPaths::getRegionOrder(all_extrusions, params.config.external_perimeters_first);
for (auto [before, after] : extrusions_constrains) { for (auto [before, after] : extrusions_constrains) {
auto after_it = map_extrusion_to_idx.find(after); auto after_it = map_extrusion_to_idx.find(after);
++blocked[after_it->second]; ++blocked[after_it->second];

View File

@ -6,6 +6,8 @@
#include <random> #include <random>
#include <algorithm> #include <algorithm>
#include <ankerl/unordered_dense.h>
namespace Slic3r { namespace Slic3r {
void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_count) { void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_count) {
@ -155,7 +157,7 @@ void its_short_edge_collpase(indexed_triangle_set &mesh, size_t target_triangle_
} }
//Extract the result mesh //Extract the result mesh
std::unordered_map<size_t, size_t> final_vertices_mapping; ankerl::unordered_dense::map<size_t, size_t> final_vertices_mapping;
std::vector<Vec3f> final_vertices; std::vector<Vec3f> final_vertices;
std::vector<Vec3i> final_indices; std::vector<Vec3i> final_indices;
final_indices.reserve(face_indices.size()); final_indices.reserve(face_indices.size());