Merge branch 'lh_voronoi_refactoring' into master_27x

This commit is contained in:
Lukáš Hejl 2024-02-08 15:41:02 +01:00
commit 054e932d34
27 changed files with 1668 additions and 1724 deletions

View File

@ -10,8 +10,6 @@
#include <functional>
#include <boost/log/trivial.hpp>
#include "utils/VoronoiUtils.hpp"
#include "utils/linearAlg2D.hpp"
#include "Utils.hpp"
#include "SVG.hpp"
@ -19,27 +17,10 @@
#include "Geometry/VoronoiUtilsCgal.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).
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
{
@ -108,8 +89,7 @@ static void export_graph_to_svg(const std::string
}
#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);
if (he_node_it == vd_node_to_he_node.end())
{
@ -124,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());
if (he_edge_it != vd_edge_to_he_edge.end())
{ // Twin segment(s) have already been made
@ -235,23 +214,20 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type&
}
}
Points 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
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.*/
const vd_t::cell_type* left_cell = vd_edge.cell();
const vd_t::cell_type* right_cell = vd_edge.twin()->cell();
const VD::cell_type *left_cell = vd_edge.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());
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());
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 = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
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_right = right_cell->contains_point();
if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment
@ -260,20 +236,20 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const
}
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);
const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments);
return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle);
Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end());
const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end());
return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle);
}
else //This is a straight edge between two points.
{
/*While the edge is straight, it is still discretized since the part
becomes narrower between the two points. As such it may need different
beadings along the way.*/
Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments);
Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments);
coord_t d = (right_point - left_point).cast<int64_t>().norm();
Point middle = (left_point + right_point) / 2;
Point x_axis_dir = perp(Point(right_point - left_point));
Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end());
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();
Point middle = (left_point + right_point) / 2;
Point x_axis_dir = perp(Point(right_point - left_point));
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.
@ -350,8 +326,7 @@ Points SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const
}
}
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())
return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell.
@ -359,16 +334,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
// 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 (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.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
const Point source_point = VoronoiUtils::getSourcePoint(cell, segments);
const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments);
Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0());
const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end());
const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end());
Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0());
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.
//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.
@ -377,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))
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 {
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;
end_source_point = source_point;
starting_vd_edge = vd_edge->next();
ending_vd_edge = vd_edge;
} 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());
@ -395,47 +370,6 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
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,
double transitioning_angle, coord_t discretization_step_size,
coord_t transition_filter_dist, coord_t allowed_filter_deviation,
@ -450,195 +384,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
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;
}
using PointMap = SkeletalTrapezoidation::PointMap;
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
const double fix_angle,
const PointMap &vertex_mapping)
{
for (STHalfEdgeNode &node : graph.nodes) {
// 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);
}
}
bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments)
{
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
continue; // Degenerated cell, there is no spoon
if (!cell.contains_segment())
continue; // Skip cells that don't contain segments.
const VoronoiUtils::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments);
const Vec2d source_segment_from = source_segment.from().cast<double>();
const Vec2d source_segment_vec = source_segment.to().cast<double>() - source_segment_from;
Point start_source_point, end_source_point;
VoronoiUtils::vd_t::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr;
SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments);
// All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid.
// FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases.
// It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf).
if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr)
for (VoronoiUtils::vd_t::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next())
if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0)
return true;
}
return false;
}
enum class VoronoiDiagramStatus {
NO_ISSUE_DETECTED,
MISSING_VORONOI_VERTEX,
NON_PLANAR_VORONOI_DIAGRAM,
VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT,
OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION
};
// 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.
VoronoiDiagramStatus detect_voronoi_diagram_known_issues(const Geometry::VoronoiDiagram &voronoi_diagram,
const std::vector<SkeletalTrapezoidation::Segment> &segments)
{
if (const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments); has_missing_voronoi_vertex) {
return VoronoiDiagramStatus::MISSING_VORONOI_VERTEX;
} else if (const bool has_voronoi_edge_intersecting_input_segment = detect_voronoi_edge_intersecting_input_segment(voronoi_diagram, segments); has_voronoi_edge_intersecting_input_segment) {
// Detection if Voronoi edge is intersecting input segment detects at least one model in GH issue #8446.
return VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT;
} else if (const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segments); !is_voronoi_diagram_planar) {
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
return VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM;
}
return VoronoiDiagramStatus::NO_ISSUE_DETECTED;
}
inline static std::pair<PointMap, double> try_to_fix_degenerated_voronoi_diagram_by_rotation(
Geometry::VoronoiDiagram &voronoi_diagram,
const Polygons &polys,
Polygons &polys_rotated,
std::vector<SkeletalTrapezoidation::Segment> &segments,
const std::vector<double> &fix_angles)
{
const Polygons polys_rotated_original = polys_rotated;
double fixed_by_angle = fix_angles.front();
PointMap vertex_mapping;
for (const double &fix_angle : fix_angles) {
vertex_mapping.clear();
polys_rotated = polys_rotated_original;
fixed_by_angle = fix_angle;
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
if (detect_voronoi_diagram_known_issues(voronoi_diagram, segments) == VoronoiDiagramStatus::NO_ISSUE_DETECTED)
break;
}
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
return {vertex_mapping, fixed_by_angle};
}
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
{
#ifdef ARACHNE_DEBUG
@ -670,8 +415,8 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
}
#endif
Geometry::VoronoiDiagram voronoi_diagram;
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
VD voronoi_diagram;
voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
#ifdef ARACHNE_DEBUG_VORONOI
{
@ -680,45 +425,15 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
}
#endif
// When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is
// intersecting input segment, rotate the input polygon and try again.
VoronoiDiagramStatus status = detect_voronoi_diagram_known_issues(voronoi_diagram, segments);
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
double fixed_by_angle = fix_angles.front();
PointMap vertex_mapping;
// polys_copy is referenced through items stored in the std::vector segments.
Polygons polys_copy = polys;
if (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
if (status == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX)
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
else if (status == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM)
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
else if (status == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles);
VoronoiDiagramStatus status_after_fix = detect_voronoi_diagram_known_issues(voronoi_diagram, segments);
assert(status_after_fix == VoronoiDiagramStatus::NO_ISSUE_DETECTED);
if (status_after_fix == VoronoiDiagramStatus::MISSING_VORONOI_VERTEX)
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
else if (status_after_fix == VoronoiDiagramStatus::NON_PLANAR_VORONOI_DIAGRAM)
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
else if (status_after_fix == VoronoiDiagramStatus::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
}
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());
for (vd_t::cell_type cell : voronoi_diagram.cells()) {
for (const VD::cell_type &cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
continue; // There is no spoon
Point start_source_point;
Point end_source_point;
vd_t::edge_type* starting_voronoi_edge = nullptr;
vd_t::edge_type* ending_voronoi_edge = nullptr;
Point start_source_point;
Point end_source_point;
const VD::edge_type *starting_voronoi_edge = nullptr;
const VD::edge_type *ending_voronoi_edge = nullptr;
// Compute and store result in above variables
if (cell.contains_point()) {
@ -727,7 +442,12 @@ process_voronoi_diagram:
continue;
} else {
assert(cell.contains_segment());
computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_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_voronoi_edge || !ending_voronoi_edge) {
@ -736,69 +456,28 @@ process_voronoi_diagram:
}
// Copy start to end edge to graph
edge_t* prev_edge = nullptr;
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*starting_voronoi_edge));
edge_t *prev_edge = nullptr;
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);
node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
starting_node->data.distance_to_boundary = 0;
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);
for (vd_t::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_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(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());
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());
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>();
Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
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);
}
assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
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);
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 (status == VoronoiDiagramStatus::NO_ISSUE_DETECTED && has_missing_twin_edge(this->graph)) {
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
status = VoronoiDiagramStatus::OTHER_TYPE_OF_VORONOI_DIAGRAM_DEGENERATION;
std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles);
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 (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED) {
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 (status != VoronoiDiagramStatus::NO_ISSUE_DETECTED)
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fixed_by_angle, vertex_mapping);
#ifdef ARACHNE_DEBUG
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
#endif

View File

@ -11,8 +11,6 @@
#include <ankerl/unordered_dense.h>
#include <Arachne/utils/VoronoiUtils.hpp>
#include "utils/HalfEdgeGraph.hpp"
#include "utils/PolygonsSegmentIndex.hpp"
#include "utils/ExtrusionJunction.hpp"
@ -26,8 +24,9 @@
//#define ARACHNE_DEBUG
//#define ARACHNE_DEBUG_VORONOI
namespace Slic3r::Arachne
{
namespace Slic3r::Arachne {
using VD = Slic3r::Geometry::VoronoiDiagram;
/*!
* Main class of the dynamic beading strategies.
@ -50,8 +49,6 @@ deposition modeling" by Kuipers et al.
*/
class SkeletalTrapezoidation
{
using pos_t = double;
using vd_t = boost::polygon::voronoi_diagram<pos_t>;
using graph_t = SkeletalTrapezoidationGraph;
using edge_t = STHalfEdge;
using node_t = STHalfEdgeNode;
@ -83,7 +80,6 @@ class SkeletalTrapezoidation
public:
using Segment = PolygonsSegmentIndex;
using PointMap = ankerl::unordered_dense::map<Point, Point, PointHash>;
using NodeSet = ankerl::unordered_dense::set<node_t*>;
/*!
@ -168,9 +164,9 @@ protected:
* 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
*/
ankerl::unordered_dense::map<vd_t::edge_type*, edge_t*> vd_edge_to_he_edge;
ankerl::unordered_dense::map<vd_t::vertex_type*, node_t*> vd_node_to_he_node;
node_t& makeNode(vd_t::vertex_type& vd_node, Point p); //!< Get the node which the VD node maps to, or create a new mapping if there wasn't any yet.
ankerl::unordered_dense::map<const VD::edge_type *, edge_t *> vd_edge_to_he_edge;
ankerl::unordered_dense::map<const VD::vertex_type *, node_t *> vd_node_to_he_node;
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):
@ -181,7 +177,7 @@ protected:
* 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.
*/
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-
@ -208,7 +204,7 @@ protected:
* \return A number of coordinates along the edge where the edge is broken
* up into discrete pieces.
*/
Points 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
@ -234,33 +230,7 @@ protected:
* /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether.
*/
static 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);
/*!
* 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.
*/
static 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);
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);
/*!
* 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
@ -603,7 +573,7 @@ protected:
*/
void generateLocalMaximaSingleBeads();
friend bool detect_voronoi_edge_intersecting_input_segment(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments);
friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<Segment> &segments);
};
} // namespace Slic3r::Arachne

View File

@ -156,8 +156,6 @@ struct PathsPointIndexLocator
}
};
using PolygonsPointIndexLocator = PathsPointIndexLocator<Polygons>;
}//namespace Slic3r::Arachne
namespace std

View File

@ -27,5 +27,24 @@ public:
} // 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

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]));
}
};
Points VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, 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 = 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(perp(ppxx));
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 Points 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

@ -214,6 +214,8 @@ set(SLIC3R_SOURCES
Geometry/Voronoi.hpp
Geometry/VoronoiOffset.cpp
Geometry/VoronoiOffset.hpp
Geometry/VoronoiUtils.hpp
Geometry/VoronoiUtils.cpp
Geometry/VoronoiVisualUtils.hpp
Int128.hpp
JumpPointSearch.cpp
@ -464,7 +466,6 @@ set(SLIC3R_SOURCES
BranchingTree/BranchingTree.hpp
BranchingTree/PointCloud.cpp
BranchingTree/PointCloud.hpp
Arachne/BeadingStrategy/BeadingStrategy.hpp
Arachne/BeadingStrategy/BeadingStrategy.cpp
Arachne/BeadingStrategy/BeadingStrategyFactory.hpp
@ -495,8 +496,9 @@ set(SLIC3R_SOURCES
Arachne/utils/PolygonsSegmentIndex.hpp
Arachne/utils/PolylineStitcher.hpp
Arachne/utils/PolylineStitcher.cpp
Arachne/utils/VoronoiUtils.hpp
Arachne/utils/VoronoiUtils.cpp
Geometry/Voronoi.cpp
Geometry/VoronoiUtils.hpp
Geometry/VoronoiUtils.cpp
Arachne/SkeletalTrapezoidation.hpp
Arachne/SkeletalTrapezoidation.cpp
Arachne/SkeletalTrapezoidationEdge.hpp

View File

@ -124,7 +124,7 @@ static constexpr const char* PRINTABLE_ATTR = "printable";
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
static constexpr const char* MM_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
static constexpr const char* KEY_ATTR = "key";
static constexpr const char* VALUE_ATTR = "value";
@ -362,7 +362,7 @@ namespace Slic3r {
std::vector<Vec3i> triangles;
std::vector<std::string> custom_supports;
std::vector<std::string> custom_seam;
std::vector<std::string> mmu_segmentation;
std::vector<std::string> mm_segmentation;
bool empty() { return vertices.empty() || triangles.empty(); }
@ -371,7 +371,7 @@ namespace Slic3r {
triangles.clear();
custom_supports.clear();
custom_seam.clear();
mmu_segmentation.clear();
mm_segmentation.clear();
}
};
@ -1830,7 +1830,7 @@ namespace Slic3r {
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
m_curr_object.geometry.mm_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MM_SEGMENTATION_ATTR));
return true;
}
@ -2320,25 +2320,25 @@ namespace Slic3r {
if (has_transform)
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
// recreate custom supports, seam and mm segmentation from previously loaded attribute
volume->supported_facets.reserve(triangles_count);
volume->seam_facets.reserve(triangles_count);
volume->mmu_segmentation_facets.reserve(triangles_count);
volume->mm_segmentation_facets.reserve(triangles_count);
for (size_t i=0; i<triangles_count; ++i) {
size_t index = volume_data.first_triangle_id + i;
assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.size());
assert(index < geometry.mmu_segmentation.size());
assert(index < geometry.mm_segmentation.size());
if (! geometry.custom_supports[index].empty())
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
if (! geometry.custom_seam[index].empty())
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
if (! geometry.mmu_segmentation[index].empty())
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]);
if (! geometry.mm_segmentation[index].empty())
volume->mm_segmentation_facets.set_triangle_from_string(i, geometry.mm_segmentation[index]);
}
volume->supported_facets.shrink_to_fit();
volume->seam_facets.shrink_to_fit();
volume->mmu_segmentation_facets.shrink_to_fit();
volume->mm_segmentation_facets.shrink_to_fit();
if (auto &es = volume_data.shape_configuration; es.has_value())
volume->emboss_shape = std::move(es);
@ -3002,12 +3002,12 @@ namespace Slic3r {
output_buffer += "\"";
}
std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i);
if (! mmu_painting_data_string.empty()) {
std::string mm_painting_data_string = volume->mm_segmentation_facets.get_triangle_as_string(i);
if (! mm_painting_data_string.empty()) {
output_buffer += " ";
output_buffer += MMU_SEGMENTATION_ATTR;
output_buffer += MM_SEGMENTATION_ATTR;
output_buffer += "=\"";
output_buffer += mmu_painting_data_string;
output_buffer += mm_painting_data_string;
output_buffer += "\"";
}

View File

@ -6,6 +6,9 @@
#include "clipper.hpp"
#include "VoronoiOffset.hpp"
#include "ClipperUtils.hpp"
#include <boost/log/trivial.hpp>
#ifdef SLIC3R_DEBUG
namespace boost { namespace polygon {
@ -467,7 +470,20 @@ void MedialAxis::build(ThickPolylines* polylines)
test(l.b.y());
}
#endif // NDEBUG
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
// For several ExPolygons in SPE-1729, an invalid Voronoi diagram was produced that wasn't fixable by rotating input data.
// Those ExPolygons contain very thin lines and holes formed by very close (1-5nm) vertices that are on the edge of our resolution.
// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid.
// So we filter out such thin lines and holes and try to compute the Voronoi diagram again.
if (!m_vd.is_valid()) {
m_lines = to_lines(closing_ex({m_expolygon}, float(2. * SCALED_EPSILON)));
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
if (!m_vd.is_valid())
BOOST_LOG_TRIVIAL(error) << "MedialAxis - Invalid Voronoi diagram even after morphological closing.";
}
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
// 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

@ -8,10 +8,8 @@
#include "../Line.hpp"
#include "../Polyline.hpp"
#define BOOST_VORONOI_USE_GMP 1
#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(disable : 4146)
#endif // _MSC_VER
@ -20,18 +18,182 @@
#pragma warning(pop)
#endif // _MSC_VER
namespace Slic3r {
namespace Slic3r::Geometry {
namespace Geometry {
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
class VoronoiDiagram
{
public:
typedef double coord_type;
typedef boost::polygon::point_data<coordinate_type> point_type;
typedef boost::polygon::segment_data<coordinate_type> segment_type;
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
using coord_type = double;
using voronoi_diagram_type = boost::polygon::voronoi_diagram<coord_type>;
using point_type = boost::polygon::point_data<voronoi_diagram_type::coordinate_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.
};
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 Slicer::Geometry
} // 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_

View File

@ -0,0 +1,281 @@
#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

@ -7,15 +7,26 @@
#include <CGAL/Surface_sweep_2_algorithms.h>
#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"
using VD = Slic3r::Geometry::VoronoiDiagram;
using namespace Slic3r::Arachne;
namespace Slic3r::Geometry {
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesConstIt = ColoredLines::const_iterator;
// 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
@ -121,30 +132,30 @@ using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrie
using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered;
using CGAL_Point = impl::K::Point_2;
inline static CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
inline static CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
inline static CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
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 static Linef make_linef(const VD::edge_type &edge)
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 static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.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.
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
{
using CGAL_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
using CGAL_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_Point { return {pt.x(), pt.y()}; };
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(),
[](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());
for (const VD::edge_type &edge : voronoi_diagram.edges()) {
@ -163,7 +174,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
for (const VD::edge_type &edge : voronoi_diagram.edges())
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));
return intersections_pt.empty();
}
@ -178,29 +189,44 @@ struct ParabolicSegment
const CGAL::Orientation is_focus_on_left;
};
inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector<VoronoiUtils::Segment> &segments)
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::getSourcePoint(*(left_cell->contains_point() ? left_cell : right_cell), segments);
const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments);
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt)));
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);
return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side};
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};
}
inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector<VoronoiUtils::Segment> &segments) {
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, segments);
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments);
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),
@ -216,7 +242,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
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, segments);
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),
@ -230,39 +256,54 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
return orientation;
}
static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector<VoronoiUtils::Segment> &segments)
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(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0()));
assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0()));
CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments);
CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end);
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.
return orientation_of_two_edges(first, third, segments) == 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) {
// 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.
CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments);
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
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);
} else {
assert(orientation == CGAL::Orientation::RIGHT_TURN);
// 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.
CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments);
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
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);
}
}
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments)
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()) {
std::vector<const VD::edge_type *> edges;
const VD::edge_type *edge = vertex.incident_edge();
do {
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->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
edges.emplace_back(edge);
edge = edge->rot_next();
@ -271,11 +312,11 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
// Checking for CCW make sense for three and more edges.
if (edges.size() > 2) {
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 Geometry::VoronoiDiagram::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 *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
const VD::edge_type *curr_edge = *edge_it;
const VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments))
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end))
return false;
}
}

View File

@ -6,7 +6,7 @@
#define slic3r_VoronoiUtilsCgal_hpp_
#include "Voronoi.hpp"
#include "../Arachne/utils/VoronoiUtils.hpp"
#include "../Arachne/utils/PolygonsSegmentIndex.hpp"
namespace Slic3r::Geometry {
class VoronoiDiagram;
@ -18,8 +18,12 @@ public:
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.
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<Arachne::VoronoiUtils::Segment> &segments);
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

View File

@ -1237,7 +1237,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
vol->supported_facets.assign(volume->supported_facets);
vol->seam_facets.assign(volume->seam_facets);
vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets);
vol->mm_segmentation_facets.assign(volume->mm_segmentation_facets);
// Perform conversion only if the target "imperial" state is different from the current one.
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
@ -1349,7 +1349,7 @@ void ModelVolume::reset_extra_facets()
{
this->supported_facets.reset();
this->seam_facets.reset();
this->mmu_segmentation_facets.reset();
this->mm_segmentation_facets.reset();
}
@ -1915,7 +1915,7 @@ void ModelVolume::assign_new_unique_ids_recursive()
config.set_new_unique_id();
supported_facets.set_new_unique_id();
seam_facets.set_new_unique_id();
mmu_segmentation_facets.set_new_unique_id();
mm_segmentation_facets.set_new_unique_id();
}
void ModelVolume::rotate(double angle, Axis axis)
@ -2224,7 +2224,7 @@ bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObjec
{
return model_property_changed(mo, mo_new,
[](const ModelVolumeType t) { return t == ModelVolumeType::MODEL_PART; },
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mmu_segmentation_facets.timestamp_matches(mv_new.mmu_segmentation_facets); });
[](const ModelVolume &mv_old, const ModelVolume &mv_new){ return mv_old.mm_segmentation_facets.timestamp_matches(mv_new.mm_segmentation_facets); });
}
bool model_has_parameter_modifiers_in_objects(const Model &model)

View File

@ -823,8 +823,8 @@ public:
// List of seam enforcers/blockers.
FacetsAnnotation seam_facets;
// List of mesh facets painted for MMU segmentation.
FacetsAnnotation mmu_segmentation_facets;
// List of mesh facets painted for MM segmentation.
FacetsAnnotation mm_segmentation_facets;
// Is set only when volume is Embossed Text type
// Contain information how to re-create volume
@ -929,12 +929,12 @@ public:
this->config.set_new_unique_id();
this->supported_facets.set_new_unique_id();
this->seam_facets.set_new_unique_id();
this->mmu_segmentation_facets.set_new_unique_id();
this->mm_segmentation_facets.set_new_unique_id();
}
bool is_fdm_support_painted() const { return !this->supported_facets.empty(); }
bool is_seam_painted() const { return !this->seam_facets.empty(); }
bool is_mm_painted() const { return !this->mmu_segmentation_facets.empty(); }
bool is_mm_painted() const { return !this->mm_segmentation_facets.empty(); }
protected:
friend class Print;
@ -973,11 +973,11 @@ private:
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->mm_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
assert(this->id() != this->mm_segmentation_facets.id());
return true;
}
@ -1003,23 +1003,23 @@ private:
ObjectBase(other),
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets),
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mm_segmentation_facets(other.mm_segmentation_facets),
cut_info(other.cut_info), text_configuration(other.text_configuration), emboss_shape(other.emboss_shape)
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->mm_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
assert(this->id() != this->mm_segmentation_facets.id());
assert(this->id() == other.id());
assert(this->config.id() == other.config.id());
assert(this->supported_facets.id() == other.supported_facets.id());
assert(this->seam_facets.id() == other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id());
assert(this->mm_segmentation_facets.id() == other.mm_segmentation_facets.id());
this->set_material_id(other.material_id());
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
@ -1031,11 +1031,11 @@ private:
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->mm_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
assert(this->id() != this->mm_segmentation_facets.id());
assert(this->id() != other.id());
assert(this->config.id() == other.config.id());
this->set_material_id(other.material_id());
@ -1046,11 +1046,11 @@ private:
assert(this->config.id() != other.config.id());
assert(this->supported_facets.id() != other.supported_facets.id());
assert(this->seam_facets.id() != other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id());
assert(this->mm_segmentation_facets.id() != other.mm_segmentation_facets.id());
assert(this->id() != this->config.id());
assert(this->supported_facets.empty());
assert(this->seam_facets.empty());
assert(this->mmu_segmentation_facets.empty());
assert(this->mm_segmentation_facets.empty());
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@ -1058,19 +1058,19 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated.
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) {
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mm_segmentation_facets(-1), object(nullptr) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->supported_facets.id().invalid());
assert(this->seam_facets.id().invalid());
assert(this->mmu_segmentation_facets.id().invalid());
assert(this->mm_segmentation_facets.id().invalid());
}
template<class Archive> void load(Archive &ar) {
bool has_convex_hull;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
cereal::load_by_value(ar, supported_facets);
cereal::load_by_value(ar, seam_facets);
cereal::load_by_value(ar, mmu_segmentation_facets);
cereal::load_by_value(ar, mm_segmentation_facets);
cereal::load_by_value(ar, config);
cereal::load(ar, text_configuration);
cereal::load(ar, emboss_shape);
@ -1088,7 +1088,7 @@ private:
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull, cut_info);
cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mmu_segmentation_facets);
cereal::save_by_value(ar, mm_segmentation_facets);
cereal::save_by_value(ar, config);
cereal::save(ar, text_configuration);
cereal::save(ar, emboss_shape);

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,41 @@
namespace Slic3r {
class PrintObject;
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
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 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_

View File

@ -78,8 +78,8 @@ static inline void model_volume_list_copy_configs(ModelObject &model_object_dst,
mv_dst.supported_facets.assign(mv_src.supported_facets);
assert(mv_dst.seam_facets.id() == mv_src.seam_facets.id());
mv_dst.seam_facets.assign(mv_src.seam_facets);
assert(mv_dst.mmu_segmentation_facets.id() == mv_src.mmu_segmentation_facets.id());
mv_dst.mmu_segmentation_facets.assign(mv_src.mmu_segmentation_facets);
assert(mv_dst.mm_segmentation_facets.id() == mv_src.mm_segmentation_facets.id());
mv_dst.mm_segmentation_facets.assign(mv_src.mm_segmentation_facets);
//FIXME what to do with the materials?
// mv_dst.m_material_id = mv_src.m_material_id;
++ i_src;
@ -1374,7 +1374,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
std::vector<unsigned int> painting_extruders;
if (const auto &volumes = print_object.model_object()->volumes;
num_extruders > 1 &&
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume *v) { return ! v->mm_segmentation_facets.empty(); }) != volumes.end()) {
//FIXME be more specific! Don't enumerate extruders that are not used for painting!
painting_extruders.assign(num_extruders, 0);
std::iota(painting_extruders.begin(), painting_extruders.end(), 1);

View File

@ -728,7 +728,7 @@ void PrintObject::slice_volumes()
// Is any ModelVolume MMU painted?
if (const auto& volumes = this->model_object()->volumes;
m_print->config().nozzle_diameter.size() > 1 &&
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mmu_segmentation_facets.empty(); }) != volumes.end()) {
std::find_if(volumes.begin(), volumes.end(), [](const ModelVolume* v) { return !v->mm_segmentation_facets.empty(); }) != volumes.end()) {
// If XY Size compensation is also enabled, notify the user that XY Size compensation
// would not be used because the object is multi-material painted.

View File

@ -4922,7 +4922,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
for (GLVolume *vol : visible_volumes) {
const int obj_idx = vol->object_idx();
const int vol_idx = vol->volume_idx();
const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mmu_segmentation_facets.empty();
const bool render_as_painted = is_enabled_painted_thumbnail && obj_idx >= 0 && vol_idx >= 0 && !model_objects[obj_idx]->volumes[vol_idx]->mm_segmentation_facets.empty();
GLShaderProgram* shader = wxGetApp().get_shader(render_as_painted ? "mm_gouraud" : "gouraud_light");
if (shader == nullptr)
continue;
@ -4958,7 +4958,7 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const
const ModelVolume& model_volume = *model_objects[obj_idx]->volumes[vol_idx];
const size_t extruder_idx = get_extruder_color_idx(model_volume, extruders_count);
TriangleSelectorMmGui ts(model_volume.mesh(), extruders_colors, extruders_colors[extruder_idx]);
ts.deserialize(model_volume.mmu_segmentation_facets.get_data(), true);
ts.deserialize(model_volume.mm_segmentation_facets.get_data(), true);
ts.request_update_render_data();
ts.render(nullptr, model_matrix);

View File

@ -1905,7 +1905,7 @@ void ObjectList::del_info_item(const int obj_idx, InfoItemType type)
cnv->get_gizmos_manager().reset_all_states();
Plater::TakeSnapshot(plater, _L("Remove Multi Material painting"));
for (ModelVolume* mv : (*m_objects)[obj_idx]->volumes)
mv->mmu_segmentation_facets.reset();
mv->mm_segmentation_facets.reset();
break;
case InfoItemType::Sinking:
@ -2897,7 +2897,7 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
[type](const ModelVolume *mv) {
return !(type == InfoItemType::CustomSupports ? mv->supported_facets.empty() :
type == InfoItemType::CustomSeam ? mv->seam_facets.empty() :
mv->mmu_segmentation_facets.empty());
mv->mm_segmentation_facets.empty());
});
break;

View File

@ -648,7 +648,7 @@ void Preview::update_layers_slider_mode()
if ((volume->config.has("extruder") &&
volume->config.option("extruder")->getInt() != 0 && // extruder isn't default
volume->config.option("extruder")->getInt() != extruder) ||
!volume->mmu_segmentation_facets.empty())
!volume->mm_segmentation_facets.empty())
return false;
for (const auto& range : object->layer_config_ranges)

View File

@ -517,7 +517,7 @@ void GLGizmoMmuSegmentation::update_model_object() const
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->mmu_segmentation_facets.set(*m_triangle_selectors[idx].get());
updated |= mv->mm_segmentation_facets.set(*m_triangle_selectors[idx].get());
}
if (updated) {
@ -547,7 +547,7 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
size_t extruder_idx = get_extruder_color_idx(*mv, extruders_count);
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[extruder_idx]));
// Reset of TriangleSelector is done inside TriangleSelectorMmGUI's constructor, so we don't need it to perform it again in deserialize().
m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data(), false);
m_triangle_selectors.back()->deserialize(mv->mm_segmentation_facets.get_data(), false);
m_triangle_selectors.back()->request_update_render_data();
}
m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo);

View File

@ -3702,7 +3702,7 @@ bool Plater::priv::replace_volume_with_stl(int object_idx, int volume_idx, const
// We need to make sure that the painted data point to existing triangles.
new_volume->supported_facets.assign(old_volume->supported_facets);
new_volume->seam_facets.assign(old_volume->seam_facets);
new_volume->mmu_segmentation_facets.assign(old_volume->mmu_segmentation_facets);
new_volume->mm_segmentation_facets.assign(old_volume->mm_segmentation_facets);
}
std::swap(old_model_object->volumes[volume_idx], old_model_object->volumes.back());
old_model_object->delete_volume(old_model_object->volumes.size() - 1);
@ -7918,10 +7918,10 @@ void Plater::clear_before_change_mesh(int obj_idx, const std::string &notificati
// may be different and they would make no sense.
bool paint_removed = false;
for (ModelVolume* mv : mo->volumes) {
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mmu_segmentation_facets.empty();
paint_removed |= ! mv->supported_facets.empty() || ! mv->seam_facets.empty() || ! mv->mm_segmentation_facets.empty();
mv->supported_facets.reset();
mv->seam_facets.reset();
mv->mmu_segmentation_facets.reset();
mv->mm_segmentation_facets.reset();
}
if (paint_removed) {
// snapshot_time is captured by copy so the lambda knows where to undo/redo to.

View File

@ -62,7 +62,7 @@ TEST_CASE("Voronoi missing edges - points 12067", "[Voronoi]")
// Construction of the Voronoi Diagram.
VD vd;
construct_voronoi(pts.begin(), pts.end(), &vd);
vd.construct_voronoi(pts.begin(), pts.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-pts.svg").c_str(),
@ -190,7 +190,7 @@ TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]")
Lines lines = to_lines(poly);
VD vd;
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-lines.svg").c_str(),
@ -298,7 +298,7 @@ TEST_CASE("Voronoi weirdness", "[Voronoi]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-weirdness.svg").c_str(),
@ -322,7 +322,7 @@ TEST_CASE("Voronoi division by zero 12903", "[Voronoi]")
}
VD vd;
construct_voronoi(pts.begin(), pts.end(), &vd);
vd.construct_voronoi(pts.begin(), pts.end());
#ifdef VORONOI_DEBUG_OUT
// Scale the voronoi vertices and input points, so that the dump_voronoi_to_svg will display them correctly.
@ -1319,7 +1319,7 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]")
#endif
VD vd;
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
for (const auto& edge : vd.edges())
if (edge.is_finite()) {
@ -1360,7 +1360,7 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]")
VD vd;
Lines lines = to_lines(poly_with_hole);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 1, 1 },
@ -1426,7 +1426,7 @@ TEST_CASE("Voronoi offset 2", "[VoronoiOffset]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 },
@ -1496,7 +1496,7 @@ TEST_CASE("Voronoi offset 3", "[VoronoiOffset]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 },
@ -1747,7 +1747,7 @@ TEST_CASE("Voronoi offset with edge collapse", "[VoronoiOffset4]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 },
@ -1858,7 +1858,7 @@ TEST_CASE("Voronoi offset 5", "[VoronoiOffset5]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
for (const OffsetTest &ot : {
OffsetTest { scale_(2.8), 1, 1 },
@ -1916,7 +1916,7 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
@ -1966,7 +1966,7 @@ TEST_CASE("Voronoi missing vertex 1", "[VoronoiMissingVertex1]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex1-out.svg").c_str(), vd, Points(), lines);
#endif
@ -2006,7 +2006,7 @@ TEST_CASE("Voronoi missing vertex 2", "[VoronoiMissingVertex2]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex2-out.svg").c_str(), vd, Points(), lines);
#endif
@ -2047,7 +2047,7 @@ TEST_CASE("Voronoi missing vertex 3", "[VoronoiMissingVertex3]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex3-out.svg").c_str(), vd, Points(), lines);
#endif
@ -2091,8 +2091,8 @@ TEST_CASE("Voronoi missing vertex 4", "[VoronoiMissingVertex4]")
Geometry::VoronoiDiagram vd_2;
Lines lines_1 = to_lines(polygon_1);
Lines lines_2 = to_lines(polygon_2);
construct_voronoi(lines_1.begin(), lines_1.end(), &vd_1);
construct_voronoi(lines_2.begin(), lines_2.end(), &vd_2);
vd_1.construct_voronoi(lines_1.begin(), lines_1.end());
vd_2.construct_voronoi(lines_2.begin(), lines_2.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-1-out.svg").c_str(), vd_1, Points(), lines_1);
dump_voronoi_to_svg(debug_out_path("voronoi-missing-vertex4-2-out.svg").c_str(), vd_2, Points(), lines_2);
@ -2124,7 +2124,7 @@ TEST_CASE("Duplicate Voronoi vertices", "[Voronoi]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-duplicate-vertices-out.svg").c_str(), vd, Points(), lines);
#endif
@ -2164,7 +2164,7 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-intersecting-edges-out.svg").c_str(), vd, Points(), lines);
#endif
@ -2226,10 +2226,66 @@ TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines);
#endif
// REQUIRE(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(vd));
}
// This case is extracted from SPE-1729, where several ExPolygon with very thin lines
// and holes formed by very close (1-5nm) vertices that are on the edge of our resolution.
// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid.
TEST_CASE("Invalid Voronoi diagram - Thin lines - SPE-1729", "[InvalidVoronoiDiagramThinLinesSPE1729]")
{
Polygon contour = {
{32247689, -2405501},
{32247733, -2308514},
{32247692, -2405496},
{50484384, 332941},
{50374839, 1052546},
{32938040, -1637993},
{32938024, -1673788},
{32942107, 7220481},
{32252205, 7447599},
{32252476, 8037808},
{32555965, 8277599},
{17729260, 8904718},
{17729236, 8853233},
{17729259, 8904722},
{17039259, 8935481},
{17033440, -3880421},
{17204385, -3852156},
{17723645, -3441873},
{17723762, -3187210},
{17728957, 8240730},
{17728945, 8213866},
{31716233, 7614090},
{20801623, -1009882},
{21253963, -1580792},
{32252082, 7157187},
{32248022, -1673787},
{24245653, -2925506},
{18449246, -3809095},
{18728385, -4449246}
};
Polygon hole = {
{32247789, -2181284},
{32247870, -2003865},
{32247872, -2003866},
{32247752, -2267007}
};
Polygons polygons = {contour, hole};
VD vd;
Lines lines = to_lines(polygons);
vd.construct_voronoi(lines.begin(), lines.end());
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("invalid-voronoi-diagram-thin-lines.svg").c_str(), vd, Points(), lines);
#endif
// REQUIRE(vd.is_valid());
}