ENH: modify the multi-material segmentation and voronoi

This patch is cherry pick from Prusa, thanks to Prusa

Rework multi-material segmentation to work directly on the Voronoi diagram without creating a copy of it.

Previous algorithms assume that they can get an invalid Voronoi diagram. Because of that, during the multi-material segmentation, a copy of the Voronoi diagram was created, and there were several attempts to fix missing vertices and edges. But as it shows, this wasn't a good enough approach and sometimes led to several issues like bleeding layers.

After generalization, our approach for detection and repairs of invalid Voronoi diagrams from Arachne, we could assume that multi-material segmentation gets non-invalid Voronoi diagrams.
With this assumption, we reimplement multi-materials segmentation to work directly on the Voronoi diagram. That should make multi-material segmentation more stable.

So, this should fix several issues like bleeding layers. Also, memory consumption should decrease by a lot. Also, there should be some speedup of multi-materials segmentation.

Jira: none
Change-Id: I72aa6e1f9634d9ee8759aa469a0b39a36ace62f5
This commit is contained in:
Lukas Matena 2024-03-27 20:48:03 +08:00 committed by Lane.Wei
parent 309010fff2
commit 551ba96205
18 changed files with 3304 additions and 1682 deletions

7
src/ankerl/README.txt Normal file
View File

@ -0,0 +1,7 @@
THIS DIRECTORY CONTAINS PIECES OF THE
ankerl::unordered_dense::{map, set}
https://github.com/martinus/unordered_dense
unordered_dense 3.1.1 10782bfc651c2bb75b11bf90491f50da122e5432
SOURCE DISTRIBUTION.
THIS IS NOT THE COMPLETE unordered_dense DISTRIBUTION. ONLY FILES NEEDED FOR COMPILING PRUSASLICER WERE PUT INTO THE PRUSASLICER SOURCE DISTRIBUTION.

1584
src/ankerl/unordered_dense.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,14 +5,11 @@
#include <stack> #include <stack>
#include <functional> #include <functional>
#include <unordered_set>
#include <sstream> #include <sstream>
#include <queue> #include <queue>
#include <functional> #include <functional>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include "utils/VoronoiUtils.hpp"
#include "utils/linearAlg2D.hpp" #include "utils/linearAlg2D.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "SVG.hpp" #include "SVG.hpp"
@ -20,27 +17,10 @@
#include "Geometry/VoronoiUtilsCgal.hpp" #include "Geometry/VoronoiUtilsCgal.hpp"
#include "../EdgeGrid.hpp" #include "../EdgeGrid.hpp"
#include "Geometry/VoronoiUtils.hpp"
#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). #define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance).
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef coord_t coordinate_type;
typedef Slic3r::Point point_type;
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
{
return dir.to_int() ? CSegment.p() : CSegment.next().p();
}
};
} // namespace boost::polygon
namespace Slic3r::Arachne namespace Slic3r::Arachne
{ {
@ -109,8 +89,7 @@ static void export_graph_to_svg(const std::string
} }
#endif #endif
SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_type& vd_node, Point p) SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::vertex_type &vd_node, Point p) {
{
auto he_node_it = vd_node_to_he_node.find(&vd_node); auto he_node_it = vd_node_to_he_node.find(&vd_node);
if (he_node_it == vd_node_to_he_node.end()) if (he_node_it == vd_node_to_he_node.end())
{ {
@ -125,8 +104,7 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_ty
} }
} }
void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments) void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments) {
{
auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin()); auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin());
if (he_edge_it != vd_edge_to_he_edge.end()) if (he_edge_it != vd_edge_to_he_edge.end())
{ // Twin segment(s) have already been made { // Twin segment(s) have already been made
@ -181,7 +159,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type&
} }
else else
{ {
std::vector<Point> discretized = discretize(vd_edge, segments); Points discretized = discretize(vd_edge, segments);
assert(discretized.size() >= 2); assert(discretized.size() >= 2);
if(discretized.size() < 2) if(discretized.size() < 2)
{ {
@ -236,45 +214,42 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type&
} }
} }
std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector<Segment>& segments) Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments)
{ {
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(vd_edge));
/*Terminology in this function assumes that the edge moves horizontally from /*Terminology in this function assumes that the edge moves horizontally from
left to right. This is not necessarily the case; the edge can go in any left to right. This is not necessarily the case; the edge can go in any
direction, but it helps to picture it in a certain direction in your head.*/ direction, but it helps to picture it in a certain direction in your head.*/
const vd_t::cell_type* left_cell = vd_edge.cell(); const VD::cell_type *left_cell = vd_edge.cell();
const vd_t::cell_type* right_cell = vd_edge.twin()->cell(); const VD::cell_type *right_cell = vd_edge.twin()->cell();
assert(VoronoiUtils::p(vd_edge.vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits<coord_t>::lowest()); Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
Point start = VoronoiUtils::p(vd_edge.vertex0()).cast<coord_t>();
Point end = VoronoiUtils::p(vd_edge.vertex1()).cast<coord_t>();
bool point_left = left_cell->contains_point(); bool point_left = left_cell->contains_point();
bool point_right = right_cell->contains_point(); bool point_right = right_cell->contains_point();
if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment
{ {
return std::vector<Point>({ start, end }); return Points({ start, end });
} }
else if (point_left != point_right) //This is a parabolic edge between a point and a line. else if (point_left != point_right) //This is a parabolic edge between a point and a line.
{ {
Point p = VoronoiUtils::getSourcePoint(*(point_left ? left_cell : right_cell), segments); Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end());
const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments); const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end());
return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle); return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle);
} }
else //This is a straight edge between two points. else //This is a straight edge between two points.
{ {
/*While the edge is straight, it is still discretized since the part /*While the edge is straight, it is still discretized since the part
becomes narrower between the two points. As such it may need different becomes narrower between the two points. As such it may need different
beadings along the way.*/ beadings along the way.*/
Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments); Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end());
Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end());
coord_t d = (right_point - left_point).cast<int64_t>().norm(); coord_t d = (right_point - left_point).cast<int64_t>().norm();
Point middle = (left_point + right_point) / 2; Point middle = (left_point + right_point) / 2;
Point x_axis_dir = Point(right_point - left_point).rotate_90_degree_ccw(); Point x_axis_dir = perp(Point(right_point - left_point));
coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm(); coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm();
const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge. const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge.
@ -311,7 +286,7 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
//Start generating points along the edge. //Start generating points along the edge.
Point a = start; Point a = start;
Point b = end; Point b = end;
std::vector<Point> ret; Points ret;
ret.emplace_back(a); ret.emplace_back(a);
//Introduce an extra edge at the borders of the markings? //Introduce an extra edge at the borders of the markings?
@ -351,8 +326,7 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
} }
} }
bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments) bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments) {
{
if (cell.incident_edge()->is_infinite()) if (cell.incident_edge()->is_infinite())
return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell. return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell.
@ -360,16 +334,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
// Copy whole cell into graph or not at all // Copy whole cell into graph or not at all
// If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon. // If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon.
if (const vd_t::vertex_type &vert = *cell.incident_edge()->vertex0(); if (const VD::vertex_type &vert = *cell.incident_edge()->vertex0();
vert.x() >= double(std::numeric_limits<int64_t>::max()) || vert.x() <= double(std::numeric_limits<int64_t>::lowest()) || vert.x() >= double(std::numeric_limits<int64_t>::max()) || vert.x() <= double(std::numeric_limits<int64_t>::lowest()) ||
vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest())) vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest()))
return false; // Don't copy any part of this cell return false; // Don't copy any part of this cell
const Point source_point = VoronoiUtils::getSourcePoint(cell, segments); const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end());
const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments); const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end());
Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0()); Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0());
if (some_point == source_point.cast<int64_t>()) if (some_point == source_point.cast<int64_t>())
some_point = VoronoiUtils::p(cell.incident_edge()->vertex1()); some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1());
//Test if the some_point is even inside the polygon. //Test if the some_point is even inside the polygon.
//The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex. //The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex.
@ -378,16 +352,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point)) if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point))
return false; // Don't copy any part of this cell return false; // Don't copy any part of this cell
vd_t::edge_type* vd_edge = cell.incident_edge(); const VD::edge_type* vd_edge = cell.incident_edge();
do { do {
assert(vd_edge->is_finite()); assert(vd_edge->is_finite());
if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) { if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) {
start_source_point = source_point; start_source_point = source_point;
end_source_point = source_point; end_source_point = source_point;
starting_vd_edge = vd_edge->next(); starting_vd_edge = vd_edge->next();
ending_vd_edge = vd_edge; ending_vd_edge = vd_edge;
} else { } else {
assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input.");
} }
} }
while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge());
@ -396,47 +370,6 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
return true; return true;
} }
void SkeletalTrapezoidation::computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments)
{
const Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
const Point from = source_segment.from();
const Point to = source_segment.to();
// Find starting edge
// Find end edge
bool seen_possible_start = false;
bool after_start = false;
bool ending_edge_is_set_before_start = false;
vd_t::edge_type* edge = cell.incident_edge();
do {
if (edge->is_infinite())
continue;
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>() ));
if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
starting_vd_edge = edge;
seen_possible_start = true;
}
else if (seen_possible_start) {
after_start = true;
}
if (v1 == from.cast<int64_t>() && (!ending_vd_edge || ending_edge_is_set_before_start)) {
ending_edge_is_set_before_start = !after_start;
ending_vd_edge = edge;
}
} while (edge = edge->next(), edge != cell.incident_edge());
assert(starting_vd_edge && ending_vd_edge);
assert(starting_vd_edge != ending_vd_edge);
start_source_point = source_segment.to();
end_source_point = source_segment.from();
}
SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy,
double transitioning_angle, coord_t discretization_step_size, double transitioning_angle, coord_t discretization_step_size,
coord_t transition_filter_dist, coord_t allowed_filter_deviation, coord_t transition_filter_dist, coord_t allowed_filter_deviation,
@ -451,128 +384,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
constructFromPolygons(polys); constructFromPolygons(polys);
} }
static bool has_finite_edge_with_non_finite_vertex(const Geometry::VoronoiDiagram &voronoi_diagram)
{
for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) {
if (edge.is_finite()) {
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) ||
!VoronoiUtils::is_finite(*edge.vertex1()))
return true;
}
}
return false;
}
static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram))
return true;
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
continue; // There is no spoon
if (cell.contains_segment()) {
const SkeletalTrapezoidation::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
const Point from = source_segment.from();
const Point to = source_segment.to();
// Find starting edge
// Find end edge
bool seen_possible_start = false;
bool after_start = false;
bool ending_edge_is_set_before_start = false;
VoronoiUtils::vd_t::edge_type *starting_vd_edge = nullptr;
VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr;
VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge();
do {
if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1()))
continue;
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>()));
if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
starting_vd_edge = edge;
seen_possible_start = true;
} else if (seen_possible_start) {
after_start = true;
}
if (v1 == from.cast<int64_t>() && (!ending_vd_edge || ending_edge_is_set_before_start)) {
ending_edge_is_set_before_start = !after_start;
ending_vd_edge = edge;
}
} while (edge = edge->next(), edge != cell.incident_edge());
if (!starting_vd_edge || !ending_vd_edge || starting_vd_edge == ending_vd_edge)
return true;
}
}
return false;
}
static bool has_missing_twin_edge(const SkeletalTrapezoidationGraph &graph)
{
for (const auto &edge : graph.edges)
if (edge.twin == nullptr)
return true;
return false;
}
inline static std::unordered_map<Point, Point, PointHash> try_to_fix_degenerated_voronoi_diagram_by_rotation(
Geometry::VoronoiDiagram &voronoi_diagram,
const Polygons &polys,
Polygons &polys_rotated,
std::vector<SkeletalTrapezoidation::Segment> &segments,
const double fix_angle)
{
std::unordered_map<Point, Point, PointHash> vertex_mapping;
for (Polygon &poly : polys_rotated)
poly.rotate(fix_angle);
assert(polys_rotated.size() == polys.size());
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
assert(polys_rotated[poly_idx].size() == polys[poly_idx].size());
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]});
}
segments.clear();
for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++)
for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++)
segments.emplace_back(&polys_rotated, poly_idx, point_idx);
voronoi_diagram.clear();
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
#ifdef ARACHNE_DEBUG_VORONOI
{
static int iRun = 0;
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
}
#endif
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
return vertex_mapping;
}
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
const double fix_angle,
const std::unordered_map<Point, Point, PointHash> &vertex_mapping)
{
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 (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
node.p = node_it->second;
else
node.p.rotate(-fix_angle);
}
}
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys) void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
{ {
#ifdef ARACHNE_DEBUG #ifdef ARACHNE_DEBUG
@ -604,8 +415,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
} }
#endif #endif
Geometry::VoronoiDiagram voronoi_diagram; VD voronoi_diagram;
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram); voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
#ifdef ARACHNE_DEBUG_VORONOI #ifdef ARACHNE_DEBUG_VORONOI
{ {
@ -614,126 +425,59 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
} }
#endif #endif
// Try to detect cases when some Voronoi vertex is missing and when
// the Voronoi diagram is not planar.
// When any Voronoi vertex is missing, or the Voronoi diagram is not
// planar, rotate the input polygon and try again.
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram);
const double fix_angle = PI / 6;
std::unordered_map<Point, Point, PointHash> vertex_mapping;
// polys_copy is referenced through items stored in the std::vector segments.
Polygons polys_copy = polys;
if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) {
if (has_missing_voronoi_vertex)
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
else if (!is_voronoi_diagram_planar)
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram));
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram))
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
}
bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar;
process_voronoi_diagram:
assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty()); assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty());
for (vd_t::cell_type cell : voronoi_diagram.cells()) { for (const VD::cell_type &cell : voronoi_diagram.cells()) {
if (!cell.incident_edge()) if (!cell.incident_edge())
continue; // There is no spoon continue; // There is no spoon
Point start_source_point; Point start_source_point;
Point end_source_point; Point end_source_point;
vd_t::edge_type* starting_vonoroi_edge = nullptr; const VD::edge_type *starting_voronoi_edge = nullptr;
vd_t::edge_type* ending_vonoroi_edge = nullptr; const VD::edge_type *ending_voronoi_edge = nullptr;
// Compute and store result in above variables // Compute and store result in above variables
if (cell.contains_point()) { if (cell.contains_point()) {
const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments);
if (!keep_going) if (!keep_going)
continue; continue;
} else { } else {
assert(cell.contains_segment()); assert(cell.contains_segment());
computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments); Geometry::SegmentCellRange<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend());
assert(cell_range.is_valid());
start_source_point = cell_range.segment_start_point;
end_source_point = cell_range.segment_end_point;
starting_voronoi_edge = cell_range.edge_begin;
ending_voronoi_edge = cell_range.edge_end;
} }
if (!starting_vonoroi_edge || !ending_vonoroi_edge) { if (!starting_voronoi_edge || !ending_voronoi_edge) {
assert(false && "Each cell should start / end in a polygon vertex"); assert(false && "Each cell should start / end in a polygon vertex");
continue; continue;
} }
// Copy start to end edge to graph // Copy start to end edge to graph
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*starting_voronoi_edge));
edge_t *prev_edge = nullptr; edge_t *prev_edge = nullptr;
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest()); transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest()); node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()).cast<coord_t>(), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments);
node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()];
starting_node->data.distance_to_boundary = 0; starting_node->data.distance_to_boundary = 0;
constexpr bool is_next_to_start_or_end = true; constexpr bool is_next_to_start_or_end = true;
graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end);
for (vd_t::edge_type* vd_edge = starting_vonoroi_edge->next(); vd_edge != ending_vonoroi_edge; vd_edge = vd_edge->next()) { for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
assert(vd_edge->is_finite()); assert(vd_edge->is_finite());
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest()); Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
Point v1 = VoronoiUtils::p(vd_edge->vertex0()).cast<coord_t>();
Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast<coord_t>();
transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments);
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_vonoroi_edge);
} }
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments);
prev_edge->to->data.distance_to_boundary = 0; prev_edge->to->data.distance_to_boundary = 0;
} }
// For some input polygons, as in GH issues #8474 and #8514 resulting Voronoi diagram is degenerated because it is not planar.
// When this degenerated Voronoi diagram is processed, the resulting half-edge structure contains some edges that don't have
// a twin edge. Based on this, we created a fast mechanism that detects those causes and tries to recompute the Voronoi
// diagram on slightly rotated input polygons that usually make the Voronoi generator generate a non-degenerated Voronoi diagram.
if (!degenerated_voronoi_diagram && has_missing_twin_edge(this->graph)) {
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
degenerated_voronoi_diagram = true;
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input.";
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
this->graph.edges.clear();
this->graph.nodes.clear();
this->vd_edge_to_he_edge.clear();
this->vd_node_to_he_node.clear();
goto process_voronoi_diagram;
}
if (degenerated_voronoi_diagram) {
assert(!has_missing_twin_edge(this->graph));
if (has_missing_twin_edge(this->graph))
BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input.";
}
if (degenerated_voronoi_diagram)
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping);
#ifdef ARACHNE_DEBUG #ifdef ARACHNE_DEBUG
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram)); assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
#endif #endif
@ -742,7 +486,7 @@ process_voronoi_diagram:
graph.collapseSmallEdges(); graph.collapseSmallEdges();
// Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge, // Set [incident_edge] the first possible edge that way we can iterate over all reachable edges from node.incident_edge,
// without needing to iterate backward // without needing to iterate backward
for (edge_t& edge : graph.edges) for (edge_t& edge : graph.edges)
if (!edge.prev) if (!edge.prev)
@ -751,7 +495,7 @@ process_voronoi_diagram:
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)
@ -2221,16 +1965,16 @@ 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); 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,9 +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 "utils/HalfEdgeGraph.hpp" #include "utils/HalfEdgeGraph.hpp"
#include "utils/PolygonsSegmentIndex.hpp" #include "utils/PolygonsSegmentIndex.hpp"
#include "utils/ExtrusionJunction.hpp" #include "utils/ExtrusionJunction.hpp"
@ -23,8 +24,9 @@
//#define ARACHNE_DEBUG //#define ARACHNE_DEBUG
//#define ARACHNE_DEBUG_VORONOI //#define ARACHNE_DEBUG_VORONOI
namespace Slic3r::Arachne namespace Slic3r::Arachne {
{
using VD = Slic3r::Geometry::VoronoiDiagram;
/*! /*!
* Main class of the dynamic beading strategies. * Main class of the dynamic beading strategies.
@ -47,8 +49,6 @@ deposition modeling" by Kuipers et al.
*/ */
class SkeletalTrapezoidation class SkeletalTrapezoidation
{ {
using pos_t = double;
using vd_t = boost::polygon::voronoi_diagram<pos_t>;
using graph_t = SkeletalTrapezoidationGraph; using graph_t = SkeletalTrapezoidationGraph;
using edge_t = STHalfEdge; using edge_t = STHalfEdge;
using node_t = STHalfEdgeNode; using node_t = STHalfEdgeNode;
@ -80,6 +80,10 @@ class SkeletalTrapezoidation
public: public:
using Segment = PolygonsSegmentIndex; using Segment = PolygonsSegmentIndex;
using NodeSet = ankerl::unordered_dense::set<node_t *>;
using EdgeSet = ankerl::unordered_dense::set<edge_t *>;
using EdgeMap = ankerl::unordered_dense::map<const VD::edge_type *, edge_t *>;
using NodeMap = ankerl::unordered_dense::map<const VD::vertex_type *, node_t *>;
/*! /*!
* Construct a new trapezoidation problem to solve. * Construct a new trapezoidation problem to solve.
@ -163,9 +167,9 @@ 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; EdgeMap vd_edge_to_he_edge;
std::unordered_map<vd_t::vertex_type*, node_t*> vd_node_to_he_node; NodeMap 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(const VD::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.
/*! /*!
* (Eventual) returned 'polylines per index' result (from generateToolpaths): * (Eventual) returned 'polylines per index' result (from generateToolpaths):
@ -176,7 +180,7 @@ protected:
* Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges) * Transfer an edge from the VD to the HE and perform discretization of parabolic edges (and vertex-vertex edges)
* \p prev_edge serves as input and output. May be null as input. * \p prev_edge serves as input and output. May be null as input.
*/ */
void transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments); void transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments);
/*! /*!
* Discretize a Voronoi edge that represents the medial axis of a vertex- * Discretize a Voronoi edge that represents the medial axis of a vertex-
@ -203,7 +207,7 @@ protected:
* \return A number of coordinates along the edge where the edge is broken * \return A number of coordinates along the edge where the edge is broken
* up into discrete pieces. * up into discrete pieces.
*/ */
std::vector<Point> discretize(const vd_t::edge_type& segment, const std::vector<Segment>& segments); Points discretize(const VD::edge_type& segment, const std::vector<Segment>& segments);
/*! /*!
* Compute the range of line segments that surround a cell of the skeletal * Compute the range of line segments that surround a cell of the skeletal
@ -229,33 +233,7 @@ protected:
* /return Whether the cell is inside of the polygon. If it's outside of the * /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether. * polygon we should skip processing it altogether.
*/ */
bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments); static bool computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments);
/*!
* Compute the range of line segments that surround a cell of the skeletal
* graph that belongs to a line segment of the medial axis.
*
* This should only be used on cells that belong to a central line segment
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
*
* The resulting line segments is just the first and the last segment. They
* are linked to the neighboring segments, so you can iterate over the
* segments until you reach the last segment.
* \param cell The cell to compute the range of line segments for.
* \param[out] start_source_point The start point of the source segment of
* this cell.
* \param[out] end_source_point The end point of the source segment of this
* cell.
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
* loop around the cell starts.
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
* around the cell ends.
* \param points All vertices of the input Polygons.
* \param segments All edges of the input Polygons.
* /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether.
*/
void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
/*! /*!
* For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two * For VD cells associated with an input polygon vertex, we need to separate the node at the end and start of the cell into two

View File

@ -27,5 +27,24 @@ public:
} // namespace Slic3r::Arachne } // namespace Slic3r::Arachne
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::Arachne::PolygonsSegmentIndex>
{
typedef coord_t coordinate_type;
typedef Slic3r::Point point_type;
static inline point_type get(const Slic3r::Arachne::PolygonsSegmentIndex &CSegment, direction_1d dir)
{
return dir.to_int() ? CSegment.to() : CSegment.from();
}
};
} // namespace boost::polygon
#endif//UTILS_POLYGONS_SEGMENT_INDEX_H #endif//UTILS_POLYGONS_SEGMENT_INDEX_H

View File

@ -1,251 +0,0 @@
//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#include <stack>
#include <optional>
#include <boost/log/trivial.hpp>
#include "linearAlg2D.hpp"
#include "VoronoiUtils.hpp"
namespace Slic3r::Arachne
{
Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node)
{
const double x = node->x();
const double y = node->y();
assert(std::isfinite(x) && std::isfinite(y));
assert(x <= double(std::numeric_limits<int64_t>::max()) && x >= std::numeric_limits<int64_t>::lowest());
assert(y <= double(std::numeric_limits<int64_t>::max()) && y >= std::numeric_limits<int64_t>::lowest());
return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates.
}
Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
{
assert(cell.contains_point());
if(!cell.contains_point())
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
switch (cell.source_category()) {
case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT:
assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n");
BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!";
break;
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT:
assert(cell.source_index() < segments.size());
return segments[cell.source_index()].to();
break;
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT:
assert(cell.source_index() < segments.size());
return segments[cell.source_index()].from();
break;
default:
assert(false && "getSourcePoint should only be called on point cells!\n");
break;
}
assert(false && "cell.source_category() is equal to an invalid value!\n");
BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!";
return {};
}
PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
{
assert(cell.contains_point());
if(!cell.contains_point())
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT);
switch (cell.source_category()) {
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: {
assert(cell.source_index() < segments.size());
PolygonsPointIndex ret = segments[cell.source_index()];
++ret;
return ret;
break;
}
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: {
assert(cell.source_index() < segments.size());
return segments[cell.source_index()];
break;
}
default:
assert(false && "getSourcePoint should only be called on point cells!\n");
break;
}
PolygonsPointIndex ret = segments[cell.source_index()];
return ++ret;
}
const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments)
{
assert(cell.contains_segment());
if (!cell.contains_segment())
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!";
return segments[cell.source_index()];
}
class PointMatrix
{
public:
double matrix[4];
PointMatrix()
{
matrix[0] = 1;
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 1;
}
PointMatrix(double rotation)
{
rotation = rotation / 180 * M_PI;
matrix[0] = cos(rotation);
matrix[1] = -sin(rotation);
matrix[2] = -matrix[1];
matrix[3] = matrix[0];
}
PointMatrix(const Point p)
{
matrix[0] = p.x();
matrix[1] = p.y();
double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1]));
matrix[0] /= f;
matrix[1] /= f;
matrix[2] = -matrix[1];
matrix[3] = matrix[0];
}
static PointMatrix scale(double s)
{
PointMatrix ret;
ret.matrix[0] = s;
ret.matrix[3] = s;
return ret;
}
Point apply(const Point p) const
{
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3]));
}
Point unapply(const Point p) const
{
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3]));
}
};
std::vector<Point> VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle)
{
std::vector<Point> discretized;
// x is distance of point projected on the segment ab
// xx is point projected on the segment ab
const Point a = segment.from();
const Point b = segment.to();
const Point ab = b - a;
const Point as = s - a;
const Point ae = e - a;
const coord_t ab_size = ab.cast<int64_t>().norm();
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t sxex = ex - sx;
assert((as.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
assert((ae.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
const Point ap = p - a;
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
assert((ap.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
Point pxx;
Line(a, b).distance_to_infinite_squared(p, &pxx);
const Point ppxx = pxx - p;
const coord_t d = ppxx.cast<int64_t>().norm();
const PointMatrix rot = PointMatrix(ppxx.rotate_90_degree_ccw());
if (d == 0)
{
discretized.emplace_back(s);
discretized.emplace_back(e);
return discretized;
}
const float marking_bound = atan(transitioning_angle * 0.5);
int64_t msx = - marking_bound * int64_t(d); // projected marking_start
int64_t mex = marking_bound * int64_t(d); // projected marking_end
assert(msx <= std::numeric_limits<coord_t>::max());
assert(double(msx) * double(msx) <= double(std::numeric_limits<int64_t>::max()));
assert(mex <= std::numeric_limits<coord_t>::max());
assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits<coord_t>::max());
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx;
Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx;
const int dir = (sx > ex) ? -1 : 1;
if (dir < 0)
{
std::swap(marking_start, marking_end);
std::swap(msx, mex);
}
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
const Point apex = rot.unapply(Point(0, d / 2)) + pxx;
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
assert(!(add_marking_start && add_marking_end) || add_apex);
if(add_marking_start && add_marking_end && !add_apex)
{
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
}
const coord_t step_count = static_cast<coord_t>(static_cast<float>(std::abs(ex - sx)) / approximate_step_size + 0.5);
discretized.emplace_back(s);
for (coord_t step = 1; step < step_count; step++)
{
assert(double(sxex) * double(step) <= double(std::numeric_limits<int64_t>::max()));
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
assert(double(x) * double(x) <= double(std::numeric_limits<int64_t>::max()));
assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits<int64_t>::max()));
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir))
{
discretized.emplace_back(marking_start);
add_marking_start = false;
}
if (add_apex && int64_t(x) * int64_t(dir) > 0)
{
discretized.emplace_back(apex);
add_apex = false; // only add the apex just before the
}
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir))
{
discretized.emplace_back(marking_end);
add_marking_end = false;
}
assert(x <= std::numeric_limits<coord_t>::max() && x >= std::numeric_limits<coord_t>::lowest());
assert(y <= std::numeric_limits<coord_t>::max() && y >= std::numeric_limits<coord_t>::lowest());
const Point result = rot.unapply(Point(x, y)) + pxx;
discretized.emplace_back(result);
}
if (add_apex)
{
discretized.emplace_back(apex);
}
if (add_marking_end)
{
discretized.emplace_back(marking_end);
}
discretized.emplace_back(e);
return discretized;
}
}//namespace Slic3r::Arachne

View File

@ -1,47 +0,0 @@
//Copyright (c) 2020 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.
#ifndef UTILS_VORONOI_UTILS_H
#define UTILS_VORONOI_UTILS_H
#include <vector>
#include <boost/polygon/voronoi.hpp>
#include "PolygonsSegmentIndex.hpp"
namespace Slic3r::Arachne
{
/*!
*/
class VoronoiUtils
{
public:
using Segment = PolygonsSegmentIndex;
using voronoi_data_t = double;
using vd_t = boost::polygon::voronoi_diagram<voronoi_data_t>;
static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
static Vec2i64 p(const vd_t::vertex_type *node);
/*!
* Discretize a parabola based on (approximate) step size.
* The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola.
*/
static std::vector<Point> discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle);
static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex)
{
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
}
};
} // namespace Slic3r::Arachne
#endif // UTILS_VORONOI_UTILS_H

View File

@ -171,9 +171,12 @@ set(lisbslic3r_sources
Geometry/Curves.hpp Geometry/Curves.hpp
Geometry/MedialAxis.cpp Geometry/MedialAxis.cpp
Geometry/MedialAxis.hpp Geometry/MedialAxis.hpp
Geometry/Voronoi.cpp
Geometry/Voronoi.hpp Geometry/Voronoi.hpp
Geometry/VoronoiOffset.cpp Geometry/VoronoiOffset.cpp
Geometry/VoronoiOffset.hpp Geometry/VoronoiOffset.hpp
Geometry/VoronoiUtils.hpp
Geometry/VoronoiUtils.cpp
Geometry/VoronoiUtilsCgal.cpp Geometry/VoronoiUtilsCgal.cpp
Geometry/VoronoiUtilsCgal.hpp Geometry/VoronoiUtilsCgal.hpp
Geometry/VoronoiVisualUtils.hpp Geometry/VoronoiVisualUtils.hpp
@ -391,8 +394,6 @@ set(lisbslic3r_sources
Arachne/utils/PolygonsSegmentIndex.hpp Arachne/utils/PolygonsSegmentIndex.hpp
Arachne/utils/PolylineStitcher.hpp Arachne/utils/PolylineStitcher.hpp
Arachne/utils/PolylineStitcher.cpp Arachne/utils/PolylineStitcher.cpp
Arachne/utils/VoronoiUtils.hpp
Arachne/utils/VoronoiUtils.cpp
Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.hpp
Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidation.cpp
Arachne/SkeletalTrapezoidationEdge.hpp Arachne/SkeletalTrapezoidationEdge.hpp

View File

@ -448,7 +448,7 @@ MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expo
void MedialAxis::build(ThickPolylines* polylines) void MedialAxis::build(ThickPolylines* polylines)
{ {
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd); m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines); Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees // static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha); // std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);

View File

@ -0,0 +1,354 @@
#include "Voronoi.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
#include "libslic3r/Geometry/VoronoiUtils.hpp"
#include "libslic3r/Geometry/VoronoiUtilsCgal.hpp"
#include "libslic3r/MultiMaterialSegmentation.hpp"
#include <boost/log/trivial.hpp>
namespace Slic3r::Geometry {
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
// Explicit template instantiation.
template void VoronoiDiagram::construct_voronoi(LinesIt, LinesIt, bool);
template void VoronoiDiagram::construct_voronoi(ColoredLinesConstIt, ColoredLinesConstIt, bool);
template void VoronoiDiagram::construct_voronoi(PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt, bool);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
void>::type
VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const SegmentIterator segment_end, const bool try_to_repair_if_needed) {
boost::polygon::construct_voronoi(segment_begin, segment_end, &m_voronoi_diagram);
if (try_to_repair_if_needed) {
if (m_issue_type = detect_known_issues(*this, segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
BOOST_LOG_TRIVIAL(warning) << "Detected finite Voronoi vertex with non finite vertex, input polygons will be rotated back and forth.";
} else {
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth.";
}
if (m_issue_type = try_to_repair_degenerated_voronoi_diagram(segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
BOOST_LOG_TRIVIAL(error) << "Detected finite Voronoi vertex with non finite vertex even after the rotation of input.";
} else {
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input.";
}
m_state = State::REPAIR_UNSUCCESSFUL;
} else {
m_state = State::REPAIR_SUCCESSFUL;
}
} else {
m_state = State::REPAIR_NOT_NEEDED;
m_issue_type = IssueType::NO_ISSUE_DETECTED;
}
} else {
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
}
void VoronoiDiagram::clear()
{
if (m_is_modified) {
m_vertices.clear();
m_edges.clear();
m_cells.clear();
m_is_modified = false;
} else {
m_voronoi_diagram.clear();
}
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
void VoronoiDiagram::copy_to_local(voronoi_diagram_type &voronoi_diagram) {
m_edges.clear();
m_cells.clear();
m_vertices.clear();
// Copy Voronoi edges.
m_edges.reserve(voronoi_diagram.num_edges());
for (const edge_type &edge : voronoi_diagram.edges()) {
m_edges.emplace_back(edge.is_linear(), edge.is_primary());
m_edges.back().color(edge.color());
}
// Copy Voronoi cells.
m_cells.reserve(voronoi_diagram.num_cells());
for (const cell_type &cell : voronoi_diagram.cells()) {
m_cells.emplace_back(cell.source_index(), cell.source_category());
m_cells.back().color(cell.color());
if (cell.incident_edge()) {
size_t incident_edge_idx = cell.incident_edge() - voronoi_diagram.edges().data();
m_cells.back().incident_edge(&m_edges[incident_edge_idx]);
}
}
// Copy Voronoi vertices.
m_vertices.reserve(voronoi_diagram.num_vertices());
for (const vertex_type &vertex : voronoi_diagram.vertices()) {
m_vertices.emplace_back(vertex.x(), vertex.y());
m_vertices.back().color(vertex.color());
if (vertex.incident_edge()) {
size_t incident_edge_idx = vertex.incident_edge() - voronoi_diagram.edges().data();
m_vertices.back().incident_edge(&m_edges[incident_edge_idx]);
}
}
// Assign all pointers for each Voronoi edge.
for (const edge_type &old_edge : voronoi_diagram.edges()) {
size_t edge_idx = &old_edge - voronoi_diagram.edges().data();
edge_type &new_edge = m_edges[edge_idx];
if (old_edge.cell()) {
size_t cell_idx = old_edge.cell() - voronoi_diagram.cells().data();
new_edge.cell(&m_cells[cell_idx]);
}
if (old_edge.vertex0()) {
size_t vertex0_idx = old_edge.vertex0() - voronoi_diagram.vertices().data();
new_edge.vertex0(&m_vertices[vertex0_idx]);
}
if (old_edge.twin()) {
size_t twin_edge_idx = old_edge.twin() - voronoi_diagram.edges().data();
new_edge.twin(&m_edges[twin_edge_idx]);
}
if (old_edge.next()) {
size_t next_edge_idx = old_edge.next() - voronoi_diagram.edges().data();
new_edge.next(&m_edges[next_edge_idx]);
}
if (old_edge.prev()) {
size_t prev_edge_idx = old_edge.prev() - voronoi_diagram.edges().data();
new_edge.prev(&m_edges[prev_edge_idx]);
}
}
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end)
{
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) {
return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX;
} else if (const IssueType cell_issue_type = detect_known_voronoi_cell_issues(voronoi_diagram, segment_begin, segment_end); cell_issue_type != IssueType::NO_ISSUE_DETECTED) {
return cell_issue_type;
} else if (!VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segment_begin, segment_end)) {
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
return IssueType::NON_PLANAR_VORONOI_DIAGRAM;
}
return IssueType::NO_ISSUE_DETECTED;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram,
const SegmentIterator segment_begin,
const SegmentIterator segment_end)
{
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
using Point = typename boost::polygon::segment_point_type<Segment>::type;
using SegmentCellRange = SegmentCellRange<Point>;
for (VD::cell_type cell : voronoi_diagram.cells()) {
if (cell.is_degenerate() || !cell.contains_segment())
continue; // Skip degenerated cell that has no spoon. Also, skip a cell that doesn't contain a segment.
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segment_begin, segment_end); cell_range.is_valid()) {
// Detection if Voronoi edge is intersecting input segment.
// It detects this type of issue at least in GH issues #8446, #8474 and #8514.
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
const Vec2d source_segment_from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW).template cast<double>();
const Vec2d source_segment_to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH).template cast<double>();
const Vec2d source_segment_vec = source_segment_to - source_segment_from;
// All Voronoi vertices must be on the left side of the source segment, otherwise the Voronoi diagram is invalid.
for (const VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) {
if (edge->is_infinite()) {
// When there is a missing Voronoi vertex, we may encounter an infinite Voronoi edge.
// This happens, for example, in GH issue #8846.
return IssueType::MISSING_VORONOI_VERTEX;
} else if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) {
return IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT;
}
}
} else {
// When there is a missing Voronoi vertex (especially at one of the endpoints of the input segment),
// the returned cell_range is marked as invalid.
// It detects this type of issue at least in GH issue #8846.
return IssueType::MISSING_VORONOI_VERTEX;
}
}
return IssueType::NO_ISSUE_DETECTED;
}
bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram)
{
for (const voronoi_diagram_type::edge_type &edge : voronoi_diagram.edges()) {
if (edge.is_finite()) {
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || !VoronoiUtils::is_finite(*edge.vertex1()))
return true;
}
}
return false;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
IssueType issue_type = m_issue_type;
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
for (const double fix_angle : fix_angles) {
issue_type = try_to_repair_degenerated_voronoi_diagram_by_rotation(segment_begin, segment_end, fix_angle);
if (issue_type == IssueType::NO_ISSUE_DETECTED) {
return issue_type;
}
}
return issue_type;
}
inline VD::vertex_type::color_type encode_input_segment_endpoint(const VD::cell_type::source_index_type cell_source_index, const boost::polygon::direction_1d dir)
{
return (cell_source_index + 1) << 1 | (dir.to_int() ? 1 : 0);
}
template<typename SegmentIterator>
inline typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
decode_input_segment_endpoint(const VD::vertex_type::color_type color, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
using SegmentType = typename std::iterator_traits<SegmentIterator>::value_type;
using PointType = typename boost::polygon::segment_traits<SegmentType>::point_type;
const size_t segment_idx = (color >> 1) - 1;
const SegmentIterator segment_it = segment_begin + segment_idx;
const PointType source_point = boost::polygon::segment_traits<SegmentType>::get(*segment_it, ((color & 1) ? boost::polygon::HIGH :
boost::polygon::LOW));
return source_point;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const SegmentIterator segment_begin,
const SegmentIterator segment_end,
const double fix_angle)
{
using SegmentType = typename std::iterator_traits<SegmentIterator>::value_type;
using PointType = typename boost::polygon::segment_traits<SegmentType>::point_type;
// Copy all segments and rotate their vertices.
std::vector<VoronoiDiagram::Segment> segments_rotated;
segments_rotated.reserve(std::distance(segment_begin, segment_end));
for (auto segment_it = segment_begin; segment_it != segment_end; ++segment_it) {
PointType from = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::LOW);
PointType to = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::HIGH);
segments_rotated.emplace_back(from.rotated(fix_angle), to.rotated(fix_angle));
}
VoronoiDiagram::voronoi_diagram_type voronoi_diagram_rotated;
boost::polygon::construct_voronoi(segments_rotated.begin(), segments_rotated.end(), &voronoi_diagram_rotated);
this->copy_to_local(voronoi_diagram_rotated);
const IssueType issue_type = detect_known_issues(*this, segments_rotated.begin(), segments_rotated.end());
// We want to remap all Voronoi vertices at the endpoints of input segments
// to ensure that Voronoi vertices at endpoints will be preserved after rotation.
// So we assign every Voronoi vertices color to map this Vertex into input segments.
for (cell_type cell : m_cells) {
if (cell.is_degenerate())
continue;
if (cell.contains_segment()) {
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segments_rotated.begin(), segments_rotated.end()); cell_range.is_valid()) {
if (cell_range.edge_end->vertex1()->color() == 0) {
// Vertex 1 of edge_end points to the starting endpoint of the input segment (from() or line.a).
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::LOW);
cell_range.edge_end->vertex1()->color(color);
}
if (cell_range.edge_begin->vertex0()->color() == 0) {
// Vertex 0 of edge_end points to the ending endpoint of the input segment (to() or line.b).
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::HIGH);
cell_range.edge_begin->vertex0()->color(color);
}
} else {
// This could happen when there is a missing Voronoi vertex even after rotation.
assert(cell_range.is_valid());
}
}
// FIXME @hejllukas: Implement mapping also for source points and not just for source segments.
}
// Rotate all Voronoi vertices back.
// When a Voronoi vertex can be mapped to the input segment endpoint, then we don't need to do rotation back.
for (vertex_type &vertex : m_vertices) {
if (vertex.color() == 0) {
// This vertex isn't mapped to any vertex, so we rotate it back.
vertex = VoronoiUtils::make_rotated_vertex(vertex, -fix_angle);
} else {
// This vertex can be mapped to the input segment endpoint.
PointType endpoint = decode_input_segment_endpoint(vertex.color(), segment_begin, segment_end);
vertex_type endpoint_vertex{double(endpoint.x()), double(endpoint.y())};
endpoint_vertex.incident_edge(vertex.incident_edge());
endpoint_vertex.color(vertex.color());
vertex = endpoint_vertex;
}
}
// We have to clear all marked vertices because some algorithms expect that all vertices have a color equal to 0.
for (vertex_type &vertex : m_vertices)
vertex.color(0);
m_voronoi_diagram.clear();
m_is_modified = true;
return issue_type;
}
} // namespace Slic3r::Geometry

View File

@ -4,8 +4,6 @@
#include "../Line.hpp" #include "../Line.hpp"
#include "../Polyline.hpp" #include "../Polyline.hpp"
#define BOOST_VORONOI_USE_GMP 1
#ifdef _MSC_VER #ifdef _MSC_VER
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned // Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
#pragma warning(push) #pragma warning(push)
@ -16,18 +14,182 @@
#pragma warning(pop) #pragma warning(pop)
#endif // _MSC_VER #endif // _MSC_VER
namespace Slic3r { namespace Slic3r::Geometry {
namespace Geometry { class VoronoiDiagram
{
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
public: public:
typedef double coord_type; using coord_type = double;
typedef boost::polygon::point_data<coordinate_type> point_type; using voronoi_diagram_type = boost::polygon::voronoi_diagram<coord_type>;
typedef boost::polygon::segment_data<coordinate_type> segment_type; using point_type = boost::polygon::point_data<voronoi_diagram_type::coordinate_type>;
typedef boost::polygon::rectangle_data<coordinate_type> rect_type; using segment_type = boost::polygon::segment_data<voronoi_diagram_type::coordinate_type>;
using rect_type = boost::polygon::rectangle_data<voronoi_diagram_type::coordinate_type>;
using coordinate_type = voronoi_diagram_type::coordinate_type;
using vertex_type = voronoi_diagram_type::vertex_type;
using edge_type = voronoi_diagram_type::edge_type;
using cell_type = voronoi_diagram_type::cell_type;
using const_vertex_iterator = voronoi_diagram_type::const_vertex_iterator;
using const_edge_iterator = voronoi_diagram_type::const_edge_iterator;
using const_cell_iterator = voronoi_diagram_type::const_cell_iterator;
using vertex_container_type = voronoi_diagram_type::vertex_container_type;
using edge_container_type = voronoi_diagram_type::edge_container_type;
using cell_container_type = voronoi_diagram_type::cell_container_type;
enum class IssueType {
NO_ISSUE_DETECTED,
FINITE_EDGE_WITH_NON_FINITE_VERTEX,
MISSING_VORONOI_VERTEX,
NON_PLANAR_VORONOI_DIAGRAM,
VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT,
UNKNOWN // Repairs are disabled in the constructor.
}; };
} } // namespace Slicer::Geometry enum class State {
REPAIR_NOT_NEEDED, // The original Voronoi diagram doesn't have any issue.
REPAIR_SUCCESSFUL, // The original Voronoi diagram has some issues, but it was repaired.
REPAIR_UNSUCCESSFUL, // The original Voronoi diagram has some issues, but it wasn't repaired.
UNKNOWN // Repairs are disabled in the constructor.
};
VoronoiDiagram() = default;
virtual ~VoronoiDiagram() = default;
IssueType get_issue_type() const { return m_issue_type; }
State get_state() const { return m_state; }
bool is_valid() const { return m_state != State::REPAIR_UNSUCCESSFUL; }
void clear();
const vertex_container_type &vertices() const { return m_is_modified ? m_vertices : m_voronoi_diagram.vertices(); }
const edge_container_type &edges() const { return m_is_modified ? m_edges : m_voronoi_diagram.edges(); }
const cell_container_type &cells() const { return m_is_modified ? m_cells : m_voronoi_diagram.cells(); }
std::size_t num_vertices() const { return m_is_modified ? m_vertices.size() : m_voronoi_diagram.num_vertices(); }
std::size_t num_edges() const { return m_is_modified ? m_edges.size() : m_voronoi_diagram.num_edges(); }
std::size_t num_cells() const { return m_is_modified ? m_cells.size() : m_voronoi_diagram.num_cells(); }
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
void>::type
construct_voronoi(SegmentIterator segment_begin, SegmentIterator segment_end, bool try_to_repair_if_needed = true);
template<typename PointIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
void>::type
construct_voronoi(const PointIterator first, const PointIterator last)
{
boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram);
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
template<typename PointIterator, typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_and<
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<typename boost::polygon::geometry_concept<
typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type>::type,
void>::type
construct_voronoi(const PointIterator p_first, const PointIterator p_last, const SegmentIterator s_first, const SegmentIterator s_last)
{
boost::polygon::construct_voronoi(p_first, p_last, s_first, s_last, &m_voronoi_diagram);
m_state = State::UNKNOWN;
m_issue_type = IssueType::UNKNOWN;
}
// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram
// is not planar or some Voronoi edge is intersecting input segment.
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
IssueType>::type
detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
try_to_repair_degenerated_voronoi_diagram_by_rotation(SegmentIterator segment_begin, SegmentIterator segment_end, double fix_angle);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
VoronoiDiagram::IssueType>::type
try_to_repair_degenerated_voronoi_diagram(SegmentIterator segment_begin, SegmentIterator segment_end);
private:
struct Segment
{
Point from;
Point to;
Segment() = delete;
explicit Segment(const Point &from, const Point &to) : from(from), to(to) {}
};
void copy_to_local(voronoi_diagram_type &voronoi_diagram);
// Detect issues related to Voronoi cells, or that can be detected by iterating over Voronoi cells.
// The first type of issue that can be detected is a missing Voronoi vertex, especially when it is
// missing at one of the endpoints of the input segment.
// The second type of issue that can be detected is a Voronoi edge that intersects the input segment.
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
IssueType>::type
detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
static bool has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram);
voronoi_diagram_type m_voronoi_diagram;
vertex_container_type m_vertices;
edge_container_type m_edges;
cell_container_type m_cells;
bool m_is_modified = false;
State m_state = State::UNKNOWN;
IssueType m_issue_type = IssueType::UNKNOWN;
public:
using SegmentIt = std::vector<Slic3r::Geometry::VoronoiDiagram::Segment>::iterator;
friend struct boost::polygon::segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>;
};
} // namespace Slic3r::Geometry
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Geometry::VoronoiDiagram::Segment>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>
{
using coordinate_type = coord_t;
using point_type = Slic3r::Point;
using segment_type = Slic3r::Geometry::VoronoiDiagram::Segment;
static inline point_type get(const segment_type &segment, direction_1d dir) { return dir.to_int() ? segment.to : segment.from; }
};
} // namespace boost::polygon
#endif // slic3r_Geometry_Voronoi_hpp_ #endif // slic3r_Geometry_Voronoi_hpp_

View File

@ -782,9 +782,6 @@ void annotate_inside_outside(VD &vd, const Lines &lines)
for (const VD::edge_type &edge : vd.edges()) for (const VD::edge_type &edge : vd.edges())
if (edge.vertex1() == nullptr) { if (edge.vertex1() == nullptr) {
if (edge.vertex0() == nullptr)
continue;
// Infinite Voronoi edge separating two Point sites or a Point site and a Segment site. // Infinite Voronoi edge separating two Point sites or a Point site and a Segment site.
// Infinite edge is always outside and it references at least one valid vertex. // Infinite edge is always outside and it references at least one valid vertex.
assert(edge.is_infinite()); assert(edge.is_infinite());
@ -891,9 +888,6 @@ void annotate_inside_outside(VD &vd, const Lines &lines)
for (const VD::edge_type &edge : vd.edges()) { for (const VD::edge_type &edge : vd.edges()) {
assert((edge_category(edge) == EdgeCategory::Unknown) == (edge_category(edge.twin()) == EdgeCategory::Unknown)); assert((edge_category(edge) == EdgeCategory::Unknown) == (edge_category(edge.twin()) == EdgeCategory::Unknown));
if (edge_category(edge) == EdgeCategory::Unknown) { if (edge_category(edge) == EdgeCategory::Unknown) {
if (!edge.is_finite())
continue;
assert(edge.is_finite()); assert(edge.is_finite());
const VD::cell_type &cell = *edge.cell(); const VD::cell_type &cell = *edge.cell();
const VD::cell_type &cell2 = *edge.twin()->cell(); const VD::cell_type &cell2 = *edge.twin()->cell();

View File

@ -0,0 +1,283 @@
#include <boost/log/trivial.hpp>
#include <Arachne/utils/PolygonsSegmentIndex.hpp>
#include <MultiMaterialSegmentation.hpp>
#include "VoronoiUtils.hpp"
namespace Slic3r::Geometry {
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesIt = ColoredLines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
// Explicit template instantiation.
template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template VD::SegmentIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
template ColoredLinesConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float);
template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename std::iterator_traits<SegmentIterator>::reference>::type
VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
if (!cell.contains_segment())
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source segment!");
if (cell.source_index() >= size_t(std::distance(segment_begin, segment_end)))
throw Slic3r::OutOfRange("Voronoi cell source index is out of range!");
return *(segment_begin + cell.source_index());
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
if (!cell.contains_point())
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::LOW);
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::HIGH);
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
} else {
throw Slic3r::InvalidArgument("Function get_source_point() should only be called on point cells!");
}
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Arachne::PolygonsPointIndex>::type
VoronoiUtils::get_source_point_index(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
if (!cell.contains_point())
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return (*segment_it);
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
const SegmentIterator segment_it = segment_begin + cell.source_index();
return (*segment_it).next();
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
} else {
throw Slic3r::InvalidArgument("Function get_source_point_index() should only be called on point cells!");
}
}
template<typename Segment>
typename boost::polygon::enable_if<typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<Segment>::type>::type>::type,
Points>::type
VoronoiUtils::discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle)
{
Points discretized;
// x is distance of point projected on the segment ab
// xx is point projected on the segment ab
const Point a = source_segment.from();
const Point b = source_segment.to();
const Point ab = b - a;
const Point as = start - a;
const Point ae = end - a;
const coord_t ab_size = ab.cast<int64_t>().norm();
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
const coord_t sxex = ex - sx;
const Point ap = source_point - a;
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
Point pxx;
Line(a, b).distance_to_infinite_squared(source_point, &pxx);
const Point ppxx = pxx - source_point;
const coord_t d = ppxx.cast<int64_t>().norm();
const Vec2d rot = perp(ppxx).cast<double>().normalized();
const double rot_cos_theta = rot.x();
const double rot_sin_theta = rot.y();
if (d == 0) {
discretized.emplace_back(start);
discretized.emplace_back(end);
return discretized;
}
const double marking_bound = atan(transitioning_angle * 0.5);
int64_t msx = -marking_bound * int64_t(d); // projected marking_start
int64_t mex = marking_bound * int64_t(d); // projected marking_end
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
const int dir = (sx > ex) ? -1 : 1;
if (dir < 0) {
std::swap(marking_start, marking_end);
std::swap(msx, mex);
}
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx;
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
assert(!add_marking_start || !add_marking_end || add_apex);
if (add_marking_start && add_marking_end && !add_apex)
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
const coord_t step_count = lround(static_cast<double>(std::abs(ex - sx)) / approximate_step_size);
discretized.emplace_back(start);
for (coord_t step = 1; step < step_count; ++step) {
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) {
discretized.emplace_back(marking_start);
add_marking_start = false;
}
if (add_apex && int64_t(x) * int64_t(dir) > 0) {
discretized.emplace_back(apex);
add_apex = false; // only add the apex just before the
}
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) {
discretized.emplace_back(marking_end);
add_marking_end = false;
}
assert(is_in_range<coord_t>(x) && is_in_range<coord_t>(y));
const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx;
discretized.emplace_back(result);
}
if (add_apex)
discretized.emplace_back(apex);
if (add_marking_end)
discretized.emplace_back(marking_end);
discretized.emplace_back(end);
return discretized;
}
template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Geometry::SegmentCellRange<
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
using Point = typename boost::polygon::segment_point_type<Segment>::type;
using SegmentCellRange = SegmentCellRange<Point>;
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
const Point from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW);
const Point to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH);
const Vec2i64 from_i64 = from.template cast<int64_t>();
const Vec2i64 to_i64 = to.template cast<int64_t>();
// FIXME @hejllukas: Ensure that there is no infinite edge during iteration between edge_begin and edge_end.
SegmentCellRange cell_range(to, from);
// Find starting edge and end edge
bool seen_possible_start = false;
bool after_start = false;
bool ending_edge_is_set_before_start = false;
const VD::edge_type *edge = cell.incident_edge();
do {
if (edge->is_infinite())
continue;
Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0());
Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1());
assert(v0 != to_i64 || v1 != from_i64);
if (v0 == to_i64 && !after_start) { // Use the last edge which starts in source_segment.to
cell_range.edge_begin = edge;
seen_possible_start = true;
} else if (seen_possible_start) {
after_start = true;
}
if (v1 == from_i64 && (!cell_range.edge_end || ending_edge_is_set_before_start)) {
ending_edge_is_set_before_start = !after_start;
cell_range.edge_end = edge;
}
} while (edge = edge->next(), edge != cell.incident_edge());
return cell_range;
}
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex)
{
assert(vertex != nullptr);
return VoronoiUtils::to_point(*vertex);
}
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type &vertex)
{
const double x = vertex.x(), y = vertex.y();
assert(std::isfinite(x) && std::isfinite(y));
assert(is_in_range<int64_t>(x) && is_in_range<int64_t>(y));
return {std::llround(x), std::llround(y)};
}
bool VoronoiUtils::is_finite(const VD::vertex_type &vertex)
{
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
}
VD::vertex_type VoronoiUtils::make_rotated_vertex(VD::vertex_type &vertex, const double angle)
{
const double cos_a = std::cos(angle);
const double sin_a = std::sin(angle);
const double rotated_x = (cos_a * vertex.x() - sin_a * vertex.y());
const double rotated_y = (cos_a * vertex.y() + sin_a * vertex.x());
VD::vertex_type rotated_vertex{rotated_x, rotated_y};
rotated_vertex.incident_edge(vertex.incident_edge());
rotated_vertex.color(vertex.color());
return rotated_vertex;
}
} // namespace Slic3r::Geometry

View File

@ -0,0 +1,120 @@
#ifndef slic3r_VoronoiUtils_hpp_
#define slic3r_VoronoiUtils_hpp_
#include "libslic3r/Geometry/Voronoi.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
using VD = Slic3r::Geometry::VoronoiDiagram;
namespace Slic3r::Geometry {
// Represent trapezoid Voronoi cell around segment.
template<typename PT> struct SegmentCellRange
{
const PT segment_start_point; // The start point of the source segment of this cell.
const PT segment_end_point; // The end point of the source segment of this cell.
const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
const VD::edge_type *edge_end = nullptr; // The edge of the Voronoi diagram where the loop around the cell ends.
SegmentCellRange() = delete;
explicit SegmentCellRange(const PT &segment_start_point, const PT &segment_end_point)
: segment_start_point(segment_start_point), segment_end_point(segment_end_point)
{}
bool is_valid() const { return edge_begin && edge_end && edge_begin != edge_end; }
};
class VoronoiUtils
{
public:
static Vec2i64 to_point(const VD::vertex_type *vertex);
static Vec2i64 to_point(const VD::vertex_type &vertex);
static bool is_finite(const VD::vertex_type &vertex);
static VD::vertex_type make_rotated_vertex(VD::vertex_type &vertex, double angle);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename std::iterator_traits<SegmentIterator>::reference>::type
get_source_segment(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
get_source_point(const VoronoiDiagram::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Arachne::PolygonsPointIndex>::type
get_source_point_index(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
/**
* Discretize a parabola based on (approximate) step size.
*
* Adapted from CuraEngine VoronoiUtils::discretizeParabola by Tim Kuipers @BagelOrb and @Ghostkeeper.
*
* @param approximate_step_size is measured parallel to the source_segment, not along the parabola.
*/
template<typename Segment>
static typename boost::polygon::enable_if<typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<Segment>::type>::type>::type,
Points>::type
discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, coord_t approximate_step_size, float transitioning_angle);
/**
* Compute the range of line segments that surround a cell of the skeletal
* graph that belongs to a line segment of the medial axis.
*
* This should only be used on cells that belong to a central line segment
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
*
* The resulting line segments is just the first and the last segment. They
* are linked to the neighboring segments, so you can iterate over the
* segments until you reach the last segment.
*
* Adapted from CuraEngine VoronoiUtils::computePointCellRange by Tim Kuipers @BagelOrb,
* Jaime van Kessel @nallath, Remco Burema @rburema and @Ghostkeeper.
*
* @param cell The cell to compute the range of line segments for.
* @param segment_begin Begin iterator for all edges of the input Polygons.
* @param segment_end End iterator for all edges of the input Polygons.
* @return Range of line segments that surround the cell.
*/
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
Geometry::SegmentCellRange<
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
compute_segment_cell_range(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename T> static bool is_in_range(double value)
{
return double(std::numeric_limits<T>::lowest()) <= value && value <= double(std::numeric_limits<T>::max());
}
template<typename T> static bool is_in_range(const VD::vertex_type &vertex)
{
return VoronoiUtils::is_finite(vertex) && is_in_range<T>(vertex.x()) && is_in_range<T>(vertex.y());
}
template<typename T> static bool is_in_range(const VD::edge_type &edge)
{
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr)
return false;
return is_in_range<T>(*edge.vertex0()) && is_in_range<T>(*edge.vertex1());
}
};
} // namespace Slic3r::Geometry
#endif // slic3r_VoronoiUtils_hpp_

View File

@ -3,7 +3,9 @@
#include <CGAL/Surface_sweep_2_algorithms.h> #include <CGAL/Surface_sweep_2_algorithms.h>
#include "libslic3r/Geometry/Voronoi.hpp" #include "libslic3r/Geometry/Voronoi.hpp"
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp" #include "libslic3r/Geometry/VoronoiUtils.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
#include "libslic3r/MultiMaterialSegmentation.hpp"
#include "VoronoiUtilsCgal.hpp" #include "VoronoiUtilsCgal.hpp"
@ -11,18 +13,145 @@ using VD = Slic3r::Geometry::VoronoiDiagram;
namespace Slic3r::Geometry { namespace Slic3r::Geometry {
using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2; using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using CGAL_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2; using LinesIt = Lines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
inline static CGAL_Point to_cgal_point(const VD::vertex_type &pt) { return {pt.x(), pt.y()}; } // Explicit template instantiation.
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesConstIt, ColoredLinesConstIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
// The tangent vector of the parabola is computed based on the Proof of the reflective property.
// https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property
// https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663
namespace impl {
using K = CGAL::Simple_cartesian<double>;
using FK = CGAL::Simple_cartesian<CGAL::Interval_nt_advanced>;
using EK = CGAL::Simple_cartesian<CGAL::MP_Float>;
using C2E = CGAL::Cartesian_converter<K, EK>;
using C2F = CGAL::Cartesian_converter<K, FK>;
class Epick : public CGAL::Filtered_kernel_adaptor<CGAL::Type_equality_wrapper<K::Base<Epick>::Type, Epick>, true> {};
template<typename K>
inline typename K::Vector_2 calculate_parabolic_tangent_vector(
// Test point on the parabola, where the tangent will be calculated.
const typename K::Point_2 &p,
// Focus point of the parabola.
const typename K::Point_2 &f,
// Points of a directrix of the parabola.
const typename K::Point_2 &u,
const typename K::Point_2 &v,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const typename K::Orientation &tangent_orientation)
{
using RT = typename K::RT;
using Vector_2 = typename K::Vector_2;
const Vector_2 directrix_vec = v - u;
const RT directrix_vec_sqr_length = CGAL::scalar_product(directrix_vec, directrix_vec);
Vector_2 focus_vec = (f - u) * directrix_vec_sqr_length - directrix_vec * CGAL::scalar_product(directrix_vec, p - u);
Vector_2 tangent_vec = focus_vec.perpendicular(tangent_orientation);
return tangent_vec;
}
template<typename K> struct ParabolicTangentToSegmentOrientationPredicate
{
using Point_2 = typename K::Point_2;
using Vector_2 = typename K::Vector_2;
using Orientation = typename K::Orientation;
using result_type = typename K::Orientation;
result_type operator()(
// Test point on the parabola, where the tangent will be calculated.
const Point_2 &p,
// End of the linear segment (p, q), for which orientation towards the tangent to parabola will be evaluated.
const Point_2 &q,
// Focus point of the parabola.
const Point_2 &f,
// Points of a directrix of the parabola.
const Point_2 &u,
const Point_2 &v,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const Orientation &tangent_orientation) const
{
assert(tangent_orientation == CGAL::Orientation::LEFT_TURN || tangent_orientation == CGAL::Orientation::RIGHT_TURN);
Vector_2 tangent_vec = calculate_parabolic_tangent_vector<K>(p, f, u, v, tangent_orientation);
Vector_2 linear_vec = q - p;
return CGAL::sign(tangent_vec.x() * linear_vec.y() - tangent_vec.y() * linear_vec.x());
}
};
template<typename K> struct ParabolicTangentToParabolicTangentOrientationPredicate
{
using Point_2 = typename K::Point_2;
using Vector_2 = typename K::Vector_2;
using Orientation = typename K::Orientation;
using result_type = typename K::Orientation;
result_type operator()(
// Common point on both parabolas, where the tangent will be calculated.
const Point_2 &p,
// Focus point of the first parabola.
const Point_2 &f_0,
// Points of a directrix of the first parabola.
const Point_2 &u_0,
const Point_2 &v_0,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const Orientation &tangent_orientation_0,
// Focus point of the second parabola.
const Point_2 &f_1,
// Points of a directrix of the second parabola.
const Point_2 &u_1,
const Point_2 &v_1,
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
const Orientation &tangent_orientation_1) const
{
assert(tangent_orientation_0 == CGAL::Orientation::LEFT_TURN || tangent_orientation_0 == CGAL::Orientation::RIGHT_TURN);
assert(tangent_orientation_1 == CGAL::Orientation::LEFT_TURN || tangent_orientation_1 == CGAL::Orientation::RIGHT_TURN);
Vector_2 tangent_vec_0 = calculate_parabolic_tangent_vector<K>(p, f_0, u_0, v_0, tangent_orientation_0);
Vector_2 tangent_vec_1 = calculate_parabolic_tangent_vector<K>(p, f_1, u_1, v_1, tangent_orientation_1);
return CGAL::sign(tangent_vec_0.x() * tangent_vec_1.y() - tangent_vec_0.y() * tangent_vec_1.x());
}
};
using ParabolicTangentToSegmentOrientationPredicateFiltered = CGAL::Filtered_predicate<ParabolicTangentToSegmentOrientationPredicate<EK>, ParabolicTangentToSegmentOrientationPredicate<FK>, C2E, C2F>;
using ParabolicTangentToParabolicTangentOrientationPredicateFiltered = CGAL::Filtered_predicate<ParabolicTangentToParabolicTangentOrientationPredicate<EK>, ParabolicTangentToParabolicTangentOrientationPredicate<FK>, C2E, C2F>;
} // namespace impl
using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrientationPredicateFiltered;
using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered;
using CGAL_Point = impl::K::Point_2;
inline CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
inline CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
inline CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
inline Linef make_linef(const VD::edge_type &edge)
{
const VD::vertex_type *v0 = edge.vertex0();
const VD::vertex_type *v1 = edge.vertex1();
return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())};
}
[[maybe_unused]] inline bool is_equal(const VD::vertex_type &vertex_first, const VD::vertex_type &vertex_second) { return vertex_first.x() == vertex_second.x() && vertex_first.y() == vertex_second.y(); }
// FIXME Lukas H.: Also includes parabolic segments. // FIXME Lukas H.: Also includes parabolic segments.
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram) bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
{ {
using CGAL_E_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
using CGAL_E_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_E_Point { return {pt.x(), pt.y()}; };
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(), assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
[](const VD::edge_type &edge) { return edge.color() == 0; })); [](const VD::edge_type &edge) { return edge.color() == 0; }));
std::vector<CGAL_Segment> segments; std::vector<CGAL_E_Segment> segments;
segments.reserve(voronoi_diagram.num_edges()); segments.reserve(voronoi_diagram.num_edges());
for (const VD::edge_type &edge : voronoi_diagram.edges()) { for (const VD::edge_type &edge : voronoi_diagram.edges()) {
@ -30,7 +159,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
continue; continue;
if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr && if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr &&
Arachne::VoronoiUtils::is_finite(*edge.vertex0()) && Arachne::VoronoiUtils::is_finite(*edge.vertex1())) { VoronoiUtils::is_finite(*edge.vertex0()) && VoronoiUtils::is_finite(*edge.vertex1())) {
segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1())); segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1()));
edge.color(1); edge.color(1);
assert(edge.twin() != nullptr); assert(edge.twin() != nullptr);
@ -41,42 +170,136 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
for (const VD::edge_type &edge : voronoi_diagram.edges()) for (const VD::edge_type &edge : voronoi_diagram.edges())
edge.color(0); edge.color(0);
std::vector<CGAL_Point> intersections_pt; std::vector<CGAL_E_Point> intersections_pt;
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt)); CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
return intersections_pt.empty(); return intersections_pt.empty();
} }
static bool check_if_three_vectors_are_ccw(const CGAL_Point &common_pt, const CGAL_Point &pt_1, const CGAL_Point &pt_2, const CGAL_Point &test_pt) { struct ParabolicSegment
CGAL::Orientation orientation = CGAL::orientation(common_pt, pt_1, pt_2); {
const Point focus;
const Line directrix;
// Two points on the parabola;
const Linef segment;
// Indicate if focus point is on the left side or right side relative to parabolic segment endpoints.
const CGAL::Orientation is_focus_on_left;
};
template<typename SegmentIterator>
inline static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
ParabolicSegment>::type
get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
assert(edge.is_curved());
const VD::cell_type *left_cell = edge.cell();
const VD::cell_type *right_cell = edge.twin()->cell();
const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end);
const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end);
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt)));
assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN);
const Point directrix_from = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::LOW);
const Point directrix_to = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::HIGH);
return {focus_pt, Line(directrix_from, directrix_to), make_linef(edge), focus_side};
}
template<typename SegmentIterator>
inline static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
CGAL::Orientation>::type
orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0()));
CGAL::Orientation orientation;
if (edge_a.is_linear() && edge_b.is_linear()) {
orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1()));
} else if (edge_a.is_curved() && edge_b.is_curved()) {
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segment_begin, segment_end);
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segment_begin, segment_end);
orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a),
to_cgal_point(parabolic_a.focus),
to_cgal_point(parabolic_a.directrix.a),
to_cgal_point(parabolic_a.directrix.b),
parabolic_a.is_focus_on_left,
to_cgal_point(parabolic_b.focus),
to_cgal_point(parabolic_b.directrix.a),
to_cgal_point(parabolic_b.directrix.b),
parabolic_b.is_focus_on_left);
return orientation;
} else {
assert(edge_a.is_curved() != edge_b.is_curved());
const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a;
const VD::edge_type &parabolic_edge = edge_a.is_curved() ? edge_a : edge_b;
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segment_begin, segment_end);
orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()),
to_cgal_point(parabolic.focus),
to_cgal_point(parabolic.directrix.a),
to_cgal_point(parabolic.directrix.b),
parabolic.is_focus_on_left);
if (edge_b.is_curved())
orientation = CGAL::opposite(orientation);
}
return orientation;
}
template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
bool>::type
check_if_three_edges_are_ccw(const VD::edge_type &edge_first,
const VD::edge_type &edge_second,
const VD::edge_type &edge_third,
const SegmentIterator segment_begin,
const SegmentIterator segment_end)
{
assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0()));
CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end);
if (orientation == CGAL::Orientation::COLLINEAR) { if (orientation == CGAL::Orientation::COLLINEAR) {
// The first two edges are collinear, so the third edge must be on the right side on the first of them. // The first two edges are collinear, so the third edge must be on the right side on the first of them.
return CGAL::orientation(common_pt, pt_1, test_pt) == CGAL::Orientation::RIGHT_TURN; return orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end) == CGAL::Orientation::RIGHT_TURN;
} else if (orientation == CGAL::Orientation::LEFT_TURN) { } else if (orientation == CGAL::Orientation::LEFT_TURN) {
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI. // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI.
// So we need to check if test_pt isn't between them. // So we need to check if test_pt isn't between them.
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt); CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt); CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN); return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN);
} else { } else {
assert(orientation == CGAL::Orientation::RIGHT_TURN); assert(orientation == CGAL::Orientation::RIGHT_TURN);
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI. // CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI.
// So we need to check if test_pt is between them. // So we need to check if test_pt is between them.
CGAL::Orientation orientation1 = CGAL::orientation(common_pt, pt_1, test_pt); CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt); CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN); return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN);
} }
} }
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram) template<typename SegmentIterator>
typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
bool>::type
VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram,
const SegmentIterator segment_begin,
const SegmentIterator segment_end)
{ {
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
std::vector<const VD::edge_type *> edges; std::vector<const VD::edge_type *> edges;
const VD::edge_type *edge = vertex.incident_edge(); const VD::edge_type *edge = vertex.incident_edge();
do { do {
// FIXME Lukas H.: Also process parabolic segments. if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
if (edge->is_finite() && edge->is_linear() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
Arachne::VoronoiUtils::is_finite(*edge->vertex0()) && Arachne::VoronoiUtils::is_finite(*edge->vertex1()))
edges.emplace_back(edge); edges.emplace_back(edge);
edge = edge->rot_next(); edge = edge->rot_next();
@ -85,12 +308,11 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
// Checking for CCW make sense for three and more edges. // Checking for CCW make sense for three and more edges.
if (edges.size() > 2) { if (edges.size() > 2) {
for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) { for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) {
const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it); const VD::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it; const VD::edge_type *curr_edge = *edge_it;
const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it); const VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
if (!check_if_three_vectors_are_ccw(to_cgal_point(*prev_edge->vertex0()), to_cgal_point(*prev_edge->vertex1()), if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end))
to_cgal_point(*curr_edge->vertex1()), to_cgal_point(*next_edge->vertex1())))
return false; return false;
} }
} }
@ -99,5 +321,4 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
return true; return true;
} }
} // namespace Slic3r::Geometry } // namespace Slic3r::Geometry

View File

@ -2,6 +2,7 @@
#define slic3r_VoronoiUtilsCgal_hpp_ #define slic3r_VoronoiUtilsCgal_hpp_
#include "Voronoi.hpp" #include "Voronoi.hpp"
#include "../Arachne/utils/PolygonsSegmentIndex.hpp"
namespace Slic3r::Geometry { namespace Slic3r::Geometry {
class VoronoiDiagram; class VoronoiDiagram;
@ -13,8 +14,12 @@ public:
static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram); static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram);
// Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex. // Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex.
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram); template<typename SegmentIterator>
static typename boost::polygon::enable_if<
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
bool>::type
is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
}; };
} // namespace Slic3r::Geometry } // namespace Slic3r::Geometry

File diff suppressed because it is too large Load Diff

View File

@ -6,13 +6,41 @@
namespace Slic3r { namespace Slic3r {
class PrintObject; class PrintObject;
class ExPolygon; class ExPolygon;
using ExPolygons = std::vector<ExPolygon>;
struct ColoredLine
{
Line line;
int color;
int poly_idx = -1;
int local_line_idx = -1;
};
using ColoredLines = std::vector<ColoredLine>;
// Returns MMU segmentation based on painting in MMU segmentation gizmo // Returns MMU segmentation based on painting in MMU segmentation gizmo
std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback); std::vector<std::vector<ExPolygons>> multi_material_segmentation_by_painting(const PrintObject &print_object, const std::function<void()> &throw_on_cancel_callback);
} // namespace Slic3r } // namespace Slic3r
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::ColoredLine>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::ColoredLine>
{
typedef coord_t coordinate_type;
typedef Slic3r::Point point_type;
static inline point_type get(const Slic3r::ColoredLine &line, const direction_1d &dir)
{
return dir.to_int() ? line.line.b : line.line.a;
}
};
} // namespace boost::polygon
#endif // slic3r_MultiMaterialSegmentation_hpp_ #endif // slic3r_MultiMaterialSegmentation_hpp_