ENH: modify the multi-material segmentation and voronoi
This patch is cherry pick from Prusa, thanks to Prusa Rework multi-material segmentation to work directly on the Voronoi diagram without creating a copy of it. Previous algorithms assume that they can get an invalid Voronoi diagram. Because of that, during the multi-material segmentation, a copy of the Voronoi diagram was created, and there were several attempts to fix missing vertices and edges. But as it shows, this wasn't a good enough approach and sometimes led to several issues like bleeding layers. After generalization, our approach for detection and repairs of invalid Voronoi diagrams from Arachne, we could assume that multi-material segmentation gets non-invalid Voronoi diagrams. With this assumption, we reimplement multi-materials segmentation to work directly on the Voronoi diagram. That should make multi-material segmentation more stable. So, this should fix several issues like bleeding layers. Also, memory consumption should decrease by a lot. Also, there should be some speedup of multi-materials segmentation. Jira: none Change-Id: I72aa6e1f9634d9ee8759aa469a0b39a36ace62f5
This commit is contained in:
parent
309010fff2
commit
551ba96205
|
@ -0,0 +1,7 @@
|
|||
THIS DIRECTORY CONTAINS PIECES OF THE
|
||||
ankerl::unordered_dense::{map, set}
|
||||
https://github.com/martinus/unordered_dense
|
||||
unordered_dense 3.1.1 10782bfc651c2bb75b11bf90491f50da122e5432
|
||||
SOURCE DISTRIBUTION.
|
||||
|
||||
THIS IS NOT THE COMPLETE unordered_dense DISTRIBUTION. ONLY FILES NEEDED FOR COMPILING PRUSASLICER WERE PUT INTO THE PRUSASLICER SOURCE DISTRIBUTION.
|
File diff suppressed because it is too large
Load Diff
|
@ -5,14 +5,11 @@
|
|||
|
||||
#include <stack>
|
||||
#include <functional>
|
||||
#include <unordered_set>
|
||||
#include <sstream>
|
||||
#include <queue>
|
||||
#include <functional>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "utils/VoronoiUtils.hpp"
|
||||
|
||||
#include "utils/linearAlg2D.hpp"
|
||||
#include "Utils.hpp"
|
||||
#include "SVG.hpp"
|
||||
|
@ -20,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
|
||||
{
|
||||
|
||||
|
@ -109,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())
|
||||
{
|
||||
|
@ -125,8 +104,7 @@ SkeletalTrapezoidation::node_t& SkeletalTrapezoidation::makeNode(vd_t::vertex_ty
|
|||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type& vd_edge, edge_t*& prev_edge, Point& start_source_point, Point& end_source_point, const std::vector<Segment>& segments)
|
||||
{
|
||||
void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments) {
|
||||
auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin());
|
||||
if (he_edge_it != vd_edge_to_he_edge.end())
|
||||
{ // Twin segment(s) have already been made
|
||||
|
@ -181,7 +159,7 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type&
|
|||
}
|
||||
else
|
||||
{
|
||||
std::vector<Point> discretized = discretize(vd_edge, segments);
|
||||
Points discretized = discretize(vd_edge, segments);
|
||||
assert(discretized.size() >= 2);
|
||||
if(discretized.size() < 2)
|
||||
{
|
||||
|
@ -236,45 +214,42 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, vd_t::edge_type&
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_edge, const std::vector<Segment>& segments)
|
||||
Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(vd_edge));
|
||||
|
||||
/*Terminology in this function assumes that the edge moves horizontally from
|
||||
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 = VoronoiUtils::p(vd_edge.vertex0()).cast<coord_t>();
|
||||
Point end = VoronoiUtils::p(vd_edge.vertex1()).cast<coord_t>();
|
||||
Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
|
||||
Point end = Geometry::VoronoiUtils::to_point(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
|
||||
{
|
||||
return std::vector<Point>({ start, end });
|
||||
return Points({ start, end });
|
||||
}
|
||||
else if (point_left != point_right) //This is a parabolic edge between a point and a line.
|
||||
{
|
||||
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);
|
||||
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 = Point(right_point - left_point).rotate_90_degree_ccw();
|
||||
Point x_axis_dir = perp(Point(right_point - left_point));
|
||||
coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm();
|
||||
|
||||
const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge.
|
||||
|
@ -311,7 +286,7 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
|
|||
//Start generating points along the edge.
|
||||
Point a = start;
|
||||
Point b = end;
|
||||
std::vector<Point> ret;
|
||||
Points ret;
|
||||
ret.emplace_back(a);
|
||||
|
||||
//Introduce an extra edge at the borders of the markings?
|
||||
|
@ -351,8 +326,7 @@ std::vector<Point> SkeletalTrapezoidation::discretize(const vd_t::edge_type& vd_
|
|||
}
|
||||
}
|
||||
|
||||
bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments)
|
||||
{
|
||||
bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments) {
|
||||
if (cell.incident_edge()->is_infinite())
|
||||
return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell.
|
||||
|
||||
|
@ -360,16 +334,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
|
|||
// Copy whole cell into graph or not at all
|
||||
|
||||
// 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.
|
||||
|
@ -378,16 +352,16 @@ bool SkeletalTrapezoidation::computePointCellRange(vd_t::cell_type& cell, Point&
|
|||
if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point))
|
||||
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());
|
||||
|
@ -396,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,
|
||||
|
@ -451,128 +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;
|
||||
}
|
||||
|
||||
inline static std::unordered_map<Point, Point, PointHash> try_to_fix_degenerated_voronoi_diagram_by_rotation(
|
||||
Geometry::VoronoiDiagram &voronoi_diagram,
|
||||
const Polygons &polys,
|
||||
Polygons &polys_rotated,
|
||||
std::vector<SkeletalTrapezoidation::Segment> &segments,
|
||||
const double fix_angle)
|
||||
{
|
||||
std::unordered_map<Point, Point, PointHash> vertex_mapping;
|
||||
for (Polygon &poly : polys_rotated)
|
||||
poly.rotate(fix_angle);
|
||||
|
||||
assert(polys_rotated.size() == polys.size());
|
||||
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
|
||||
assert(polys_rotated[poly_idx].size() == polys[poly_idx].size());
|
||||
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
|
||||
vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]});
|
||||
}
|
||||
|
||||
segments.clear();
|
||||
for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++)
|
||||
for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++)
|
||||
segments.emplace_back(&polys_rotated, poly_idx, point_idx);
|
||||
|
||||
voronoi_diagram.clear();
|
||||
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
|
||||
|
||||
#ifdef ARACHNE_DEBUG_VORONOI
|
||||
{
|
||||
static int iRun = 0;
|
||||
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
|
||||
}
|
||||
#endif
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
return vertex_mapping;
|
||||
}
|
||||
|
||||
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
|
||||
const double fix_angle,
|
||||
const std::unordered_map<Point, Point, PointHash> &vertex_mapping)
|
||||
{
|
||||
for (STHalfEdgeNode &node : graph.nodes) {
|
||||
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
|
||||
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
|
||||
node.p = node_it->second;
|
||||
else
|
||||
node.p.rotate(-fix_angle);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||||
{
|
||||
#ifdef ARACHNE_DEBUG
|
||||
|
@ -604,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
|
||||
{
|
||||
|
@ -614,126 +425,59 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
|||
}
|
||||
#endif
|
||||
|
||||
// Try to detect cases when some Voronoi vertex is missing and when
|
||||
// the Voronoi diagram is not planar.
|
||||
// When any Voronoi vertex is missing, or the Voronoi diagram is not
|
||||
// planar, rotate the input polygon and try again.
|
||||
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
|
||||
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
|
||||
const bool is_voronoi_diagram_planar = Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram);
|
||||
const double fix_angle = PI / 6;
|
||||
|
||||
std::unordered_map<Point, Point, PointHash> vertex_mapping;
|
||||
// polys_copy is referenced through items stored in the std::vector segments.
|
||||
Polygons polys_copy = polys;
|
||||
if (has_missing_voronoi_vertex || !is_voronoi_diagram_planar) {
|
||||
if (has_missing_voronoi_vertex)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
else if (!is_voronoi_diagram_planar)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
|
||||
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
|
||||
|
||||
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram));
|
||||
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
|
||||
else if (!Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
|
||||
}
|
||||
|
||||
bool degenerated_voronoi_diagram = has_missing_voronoi_vertex || !is_voronoi_diagram_planar;
|
||||
|
||||
process_voronoi_diagram:
|
||||
assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty());
|
||||
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_vonoroi_edge = nullptr;
|
||||
vd_t::edge_type* ending_vonoroi_edge = nullptr;
|
||||
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()) {
|
||||
const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments);
|
||||
const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments);
|
||||
if (!keep_going)
|
||||
continue;
|
||||
} else {
|
||||
assert(cell.contains_segment());
|
||||
computeSegmentCellRange(cell, start_source_point, end_source_point, starting_vonoroi_edge, ending_vonoroi_edge, segments);
|
||||
Geometry::SegmentCellRange<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend());
|
||||
assert(cell_range.is_valid());
|
||||
start_source_point = cell_range.segment_start_point;
|
||||
end_source_point = cell_range.segment_end_point;
|
||||
starting_voronoi_edge = cell_range.edge_begin;
|
||||
ending_voronoi_edge = cell_range.edge_end;
|
||||
}
|
||||
|
||||
if (!starting_vonoroi_edge || !ending_vonoroi_edge) {
|
||||
if (!starting_voronoi_edge || !ending_voronoi_edge) {
|
||||
assert(false && "Each cell should start / end in a polygon vertex");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy start to end edge to graph
|
||||
edge_t* prev_edge = nullptr;
|
||||
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
transferEdge(start_source_point, VoronoiUtils::p(starting_vonoroi_edge->vertex1()).cast<coord_t>(), *starting_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
node_t* starting_node = vd_node_to_he_node[starting_vonoroi_edge->vertex0()];
|
||||
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_vonoroi_edge->next(); vd_edge != ending_vonoroi_edge; vd_edge = vd_edge->next()) {
|
||||
for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
|
||||
assert(vd_edge->is_finite());
|
||||
assert(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_vonoroi_edge);
|
||||
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
|
||||
}
|
||||
|
||||
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_vonoroi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
|
||||
transferEdge(VoronoiUtils::p(ending_vonoroi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_vonoroi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||||
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 (!degenerated_voronoi_diagram && has_missing_twin_edge(this->graph)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected degenerated Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
degenerated_voronoi_diagram = true;
|
||||
vertex_mapping = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angle);
|
||||
|
||||
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
|
||||
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex after the rotation of input.";
|
||||
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
|
||||
this->graph.edges.clear();
|
||||
this->graph.nodes.clear();
|
||||
this->vd_edge_to_he_edge.clear();
|
||||
this->vd_node_to_he_node.clear();
|
||||
|
||||
goto process_voronoi_diagram;
|
||||
}
|
||||
|
||||
if (degenerated_voronoi_diagram) {
|
||||
assert(!has_missing_twin_edge(this->graph));
|
||||
|
||||
if (has_missing_twin_edge(this->graph))
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected degenerated Voronoi diagram even after the rotation of input.";
|
||||
}
|
||||
|
||||
if (degenerated_voronoi_diagram)
|
||||
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fix_angle, vertex_mapping);
|
||||
|
||||
#ifdef ARACHNE_DEBUG
|
||||
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||||
#endif
|
||||
|
@ -742,7 +486,7 @@ process_voronoi_diagram:
|
|||
|
||||
graph.collapseSmallEdges();
|
||||
|
||||
// Set [incident_edge] the the first possible edge that way we can iterate over all reachable edges from node.incident_edge,
|
||||
// Set [incident_edge] the first possible edge that way we can iterate over all reachable edges from node.incident_edge,
|
||||
// without needing to iterate backward
|
||||
for (edge_t& edge : graph.edges)
|
||||
if (!edge.prev)
|
||||
|
@ -751,7 +495,7 @@ process_voronoi_diagram:
|
|||
|
||||
void SkeletalTrapezoidation::separatePointyQuadEndNodes()
|
||||
{
|
||||
std::unordered_set<node_t*> visited_nodes;
|
||||
NodeSet visited_nodes;
|
||||
for (edge_t& edge : graph.edges)
|
||||
{
|
||||
if (edge.prev)
|
||||
|
@ -2221,16 +1965,16 @@ void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, c
|
|||
|
||||
void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_junctions)
|
||||
{
|
||||
std::unordered_set<edge_t*> unprocessed_quad_starts(graph.edges.size() * 5 / 2);
|
||||
EdgeSet unprocessed_quad_starts(graph.edges.size() * 5 / 2);
|
||||
for (edge_t& edge : graph.edges)
|
||||
{
|
||||
if (!edge.prev)
|
||||
{
|
||||
unprocessed_quad_starts.insert(&edge);
|
||||
unprocessed_quad_starts.emplace(&edge);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<edge_t*> passed_odd_edges;
|
||||
EdgeSet passed_odd_edges;
|
||||
|
||||
while (!unprocessed_quad_starts.empty())
|
||||
{
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
#include <boost/polygon/voronoi.hpp>
|
||||
|
||||
#include <memory> // smart pointers
|
||||
#include <unordered_map>
|
||||
#include <utility> // pair
|
||||
|
||||
#include <ankerl/unordered_dense.h>
|
||||
|
||||
#include "utils/HalfEdgeGraph.hpp"
|
||||
#include "utils/PolygonsSegmentIndex.hpp"
|
||||
#include "utils/ExtrusionJunction.hpp"
|
||||
|
@ -23,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.
|
||||
|
@ -47,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;
|
||||
|
@ -80,6 +80,10 @@ class SkeletalTrapezoidation
|
|||
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using NodeSet = ankerl::unordered_dense::set<node_t *>;
|
||||
using EdgeSet = ankerl::unordered_dense::set<edge_t *>;
|
||||
using EdgeMap = ankerl::unordered_dense::map<const VD::edge_type *, edge_t *>;
|
||||
using NodeMap = ankerl::unordered_dense::map<const VD::vertex_type *, node_t *>;
|
||||
|
||||
/*!
|
||||
* Construct a new trapezoidation problem to solve.
|
||||
|
@ -163,9 +167,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
|
||||
*/
|
||||
std::unordered_map<vd_t::edge_type*, edge_t*> vd_edge_to_he_edge;
|
||||
std::unordered_map<vd_t::vertex_type*, node_t*> vd_node_to_he_node;
|
||||
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.
|
||||
EdgeMap vd_edge_to_he_edge;
|
||||
NodeMap 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):
|
||||
|
@ -176,7 +180,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-
|
||||
|
@ -203,7 +207,7 @@ protected:
|
|||
* \return A number of coordinates along the edge where the edge is broken
|
||||
* up into discrete pieces.
|
||||
*/
|
||||
std::vector<Point> discretize(const vd_t::edge_type& segment, const std::vector<Segment>& segments);
|
||||
Points discretize(const VD::edge_type& segment, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
|
@ -229,33 +233,7 @@ protected:
|
|||
* /return Whether the cell is inside of the polygon. If it's outside of the
|
||||
* polygon we should skip processing it altogether.
|
||||
*/
|
||||
bool computePointCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
|
||||
|
||||
/*!
|
||||
* Compute the range of line segments that surround a cell of the skeletal
|
||||
* graph that belongs to a line segment of the medial axis.
|
||||
*
|
||||
* This should only be used on cells that belong to a central line segment
|
||||
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
|
||||
*
|
||||
* The resulting line segments is just the first and the last segment. They
|
||||
* are linked to the neighboring segments, so you can iterate over the
|
||||
* segments until you reach the last segment.
|
||||
* \param cell The cell to compute the range of line segments for.
|
||||
* \param[out] start_source_point The start point of the source segment of
|
||||
* this cell.
|
||||
* \param[out] end_source_point The end point of the source segment of this
|
||||
* cell.
|
||||
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
|
||||
* loop around the cell starts.
|
||||
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
|
||||
* around the cell ends.
|
||||
* \param points All vertices of the input Polygons.
|
||||
* \param segments All edges of the input Polygons.
|
||||
* /return Whether the cell is inside of the polygon. If it's outside of the
|
||||
* polygon we should skip processing it altogether.
|
||||
*/
|
||||
void computeSegmentCellRange(vd_t::cell_type& cell, Point& start_source_point, Point& end_source_point, vd_t::edge_type*& starting_vd_edge, vd_t::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,251 +0,0 @@
|
|||
//Copyright (c) 2021 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
#include <stack>
|
||||
#include <optional>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "linearAlg2D.hpp"
|
||||
#include "VoronoiUtils.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
Vec2i64 VoronoiUtils::p(const vd_t::vertex_type *node)
|
||||
{
|
||||
const double x = node->x();
|
||||
const double y = node->y();
|
||||
assert(std::isfinite(x) && std::isfinite(y));
|
||||
assert(x <= double(std::numeric_limits<int64_t>::max()) && x >= std::numeric_limits<int64_t>::lowest());
|
||||
assert(y <= double(std::numeric_limits<int64_t>::max()) && y >= std::numeric_limits<int64_t>::lowest());
|
||||
return {int64_t(x + 0.5 - (x < 0)), int64_t(y + 0.5 - (y < 0))}; // Round to the nearest integer coordinates.
|
||||
}
|
||||
|
||||
Point VoronoiUtils::getSourcePoint(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(cell.contains_point());
|
||||
if(!cell.contains_point())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
|
||||
|
||||
switch (cell.source_category()) {
|
||||
case boost::polygon::SOURCE_CATEGORY_SINGLE_POINT:
|
||||
assert(false && "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!";
|
||||
break;
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT:
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()].to();
|
||||
break;
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT:
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()].from();
|
||||
break;
|
||||
default:
|
||||
assert(false && "getSourcePoint should only be called on point cells!\n");
|
||||
break;
|
||||
}
|
||||
|
||||
assert(false && "cell.source_category() is equal to an invalid value!\n");
|
||||
BOOST_LOG_TRIVIAL(error) << "cell.source_category() is equal to an invalid value!";
|
||||
return {};
|
||||
}
|
||||
|
||||
PolygonsPointIndex VoronoiUtils::getSourcePointIndex(const vd_t::cell_type& cell, const std::vector<Segment>& segments)
|
||||
{
|
||||
assert(cell.contains_point());
|
||||
if(!cell.contains_point())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source point!";
|
||||
|
||||
assert(cell.source_category() != boost::polygon::SOURCE_CATEGORY_SINGLE_POINT);
|
||||
switch (cell.source_category()) {
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT: {
|
||||
assert(cell.source_index() < segments.size());
|
||||
PolygonsPointIndex ret = segments[cell.source_index()];
|
||||
++ret;
|
||||
return ret;
|
||||
break;
|
||||
}
|
||||
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: {
|
||||
assert(cell.source_index() < segments.size());
|
||||
return segments[cell.source_index()];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "getSourcePoint should only be called on point cells!\n");
|
||||
break;
|
||||
}
|
||||
PolygonsPointIndex ret = segments[cell.source_index()];
|
||||
return ++ret;
|
||||
}
|
||||
|
||||
const VoronoiUtils::Segment &VoronoiUtils::getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments)
|
||||
{
|
||||
assert(cell.contains_segment());
|
||||
if (!cell.contains_segment())
|
||||
BOOST_LOG_TRIVIAL(debug) << "Voronoi cell doesn't contain a source segment!";
|
||||
|
||||
return segments[cell.source_index()];
|
||||
}
|
||||
|
||||
class PointMatrix
|
||||
{
|
||||
public:
|
||||
double matrix[4];
|
||||
|
||||
PointMatrix()
|
||||
{
|
||||
matrix[0] = 1;
|
||||
matrix[1] = 0;
|
||||
matrix[2] = 0;
|
||||
matrix[3] = 1;
|
||||
}
|
||||
|
||||
PointMatrix(double rotation)
|
||||
{
|
||||
rotation = rotation / 180 * M_PI;
|
||||
matrix[0] = cos(rotation);
|
||||
matrix[1] = -sin(rotation);
|
||||
matrix[2] = -matrix[1];
|
||||
matrix[3] = matrix[0];
|
||||
}
|
||||
|
||||
PointMatrix(const Point p)
|
||||
{
|
||||
matrix[0] = p.x();
|
||||
matrix[1] = p.y();
|
||||
double f = sqrt((matrix[0] * matrix[0]) + (matrix[1] * matrix[1]));
|
||||
matrix[0] /= f;
|
||||
matrix[1] /= f;
|
||||
matrix[2] = -matrix[1];
|
||||
matrix[3] = matrix[0];
|
||||
}
|
||||
|
||||
static PointMatrix scale(double s)
|
||||
{
|
||||
PointMatrix ret;
|
||||
ret.matrix[0] = s;
|
||||
ret.matrix[3] = s;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Point apply(const Point p) const
|
||||
{
|
||||
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[1]), coord_t(p.x() * matrix[2] + p.y() * matrix[3]));
|
||||
}
|
||||
|
||||
Point unapply(const Point p) const
|
||||
{
|
||||
return Point(coord_t(p.x() * matrix[0] + p.y() * matrix[2]), coord_t(p.x() * matrix[1] + p.y() * matrix[3]));
|
||||
}
|
||||
};
|
||||
std::vector<Point> VoronoiUtils::discretizeParabola(const Point& p, const Segment& segment, Point s, Point e, coord_t approximate_step_size, float transitioning_angle)
|
||||
{
|
||||
std::vector<Point> discretized;
|
||||
// x is distance of point projected on the segment ab
|
||||
// xx is point projected on the segment ab
|
||||
const Point a = segment.from();
|
||||
const Point b = segment.to();
|
||||
const Point ab = b - a;
|
||||
const Point as = s - a;
|
||||
const Point ae = e - a;
|
||||
const coord_t ab_size = ab.cast<int64_t>().norm();
|
||||
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t sxex = ex - sx;
|
||||
|
||||
assert((as.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
assert((ae.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
const Point ap = p - a;
|
||||
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
|
||||
assert((ap.cast<int64_t>().dot(ab.cast<int64_t>()) / int64_t(ab_size)) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
Point pxx;
|
||||
Line(a, b).distance_to_infinite_squared(p, &pxx);
|
||||
const Point ppxx = pxx - p;
|
||||
const coord_t d = ppxx.cast<int64_t>().norm();
|
||||
const PointMatrix rot = PointMatrix(ppxx.rotate_90_degree_ccw());
|
||||
|
||||
if (d == 0)
|
||||
{
|
||||
discretized.emplace_back(s);
|
||||
discretized.emplace_back(e);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
const float marking_bound = atan(transitioning_angle * 0.5);
|
||||
int64_t msx = - marking_bound * int64_t(d); // projected marking_start
|
||||
int64_t mex = marking_bound * int64_t(d); // projected marking_end
|
||||
|
||||
assert(msx <= std::numeric_limits<coord_t>::max());
|
||||
assert(double(msx) * double(msx) <= double(std::numeric_limits<int64_t>::max()));
|
||||
assert(mex <= std::numeric_limits<coord_t>::max());
|
||||
assert(double(msx) * double(msx) / double(2 * d) + double(d / 2) <= std::numeric_limits<coord_t>::max());
|
||||
|
||||
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
|
||||
Point marking_start = rot.unapply(Point(coord_t(msx), marking_start_end_h)) + pxx;
|
||||
Point marking_end = rot.unapply(Point(coord_t(mex), marking_start_end_h)) + pxx;
|
||||
const int dir = (sx > ex) ? -1 : 1;
|
||||
if (dir < 0)
|
||||
{
|
||||
std::swap(marking_start, marking_end);
|
||||
std::swap(msx, mex);
|
||||
}
|
||||
|
||||
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
|
||||
const Point apex = rot.unapply(Point(0, d / 2)) + pxx;
|
||||
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
|
||||
|
||||
assert(!(add_marking_start && add_marking_end) || add_apex);
|
||||
if(add_marking_start && add_marking_end && !add_apex)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
|
||||
}
|
||||
|
||||
const coord_t step_count = static_cast<coord_t>(static_cast<float>(std::abs(ex - sx)) / approximate_step_size + 0.5);
|
||||
|
||||
discretized.emplace_back(s);
|
||||
for (coord_t step = 1; step < step_count; step++)
|
||||
{
|
||||
assert(double(sxex) * double(step) <= double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
|
||||
assert(double(x) * double(x) <= double(std::numeric_limits<int64_t>::max()));
|
||||
assert(double(x) * double(x) / double(2 * d) + double(d / 2) <= double(std::numeric_limits<int64_t>::max()));
|
||||
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
|
||||
|
||||
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir))
|
||||
{
|
||||
discretized.emplace_back(marking_start);
|
||||
add_marking_start = false;
|
||||
}
|
||||
if (add_apex && int64_t(x) * int64_t(dir) > 0)
|
||||
{
|
||||
discretized.emplace_back(apex);
|
||||
add_apex = false; // only add the apex just before the
|
||||
}
|
||||
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir))
|
||||
{
|
||||
discretized.emplace_back(marking_end);
|
||||
add_marking_end = false;
|
||||
}
|
||||
assert(x <= std::numeric_limits<coord_t>::max() && x >= std::numeric_limits<coord_t>::lowest());
|
||||
assert(y <= std::numeric_limits<coord_t>::max() && y >= std::numeric_limits<coord_t>::lowest());
|
||||
const Point result = rot.unapply(Point(x, y)) + pxx;
|
||||
discretized.emplace_back(result);
|
||||
}
|
||||
if (add_apex)
|
||||
{
|
||||
discretized.emplace_back(apex);
|
||||
}
|
||||
if (add_marking_end)
|
||||
{
|
||||
discretized.emplace_back(marking_end);
|
||||
}
|
||||
discretized.emplace_back(e);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
}//namespace Slic3r::Arachne
|
|
@ -1,47 +0,0 @@
|
|||
//Copyright (c) 2020 Ultimaker B.V.
|
||||
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
|
||||
#ifndef UTILS_VORONOI_UTILS_H
|
||||
#define UTILS_VORONOI_UTILS_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include <boost/polygon/voronoi.hpp>
|
||||
|
||||
#include "PolygonsSegmentIndex.hpp"
|
||||
|
||||
namespace Slic3r::Arachne
|
||||
{
|
||||
|
||||
/*!
|
||||
*/
|
||||
class VoronoiUtils
|
||||
{
|
||||
public:
|
||||
using Segment = PolygonsSegmentIndex;
|
||||
using voronoi_data_t = double;
|
||||
using vd_t = boost::polygon::voronoi_diagram<voronoi_data_t>;
|
||||
|
||||
static Point getSourcePoint(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static const Segment &getSourceSegment(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
static PolygonsPointIndex getSourcePointIndex(const vd_t::cell_type &cell, const std::vector<Segment> &segments);
|
||||
|
||||
static Vec2i64 p(const vd_t::vertex_type *node);
|
||||
|
||||
/*!
|
||||
* Discretize a parabola based on (approximate) step size.
|
||||
* The \p approximate_step_size is measured parallel to the \p source_segment, not along the parabola.
|
||||
*/
|
||||
static std::vector<Point> discretizeParabola(const Point &source_point, const Segment &source_segment, Point start, Point end, coord_t approximate_step_size, float transitioning_angle);
|
||||
|
||||
static inline bool is_finite(const VoronoiUtils::vd_t::vertex_type &vertex)
|
||||
{
|
||||
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Arachne
|
||||
|
||||
#endif // UTILS_VORONOI_UTILS_H
|
|
@ -171,9 +171,12 @@ set(lisbslic3r_sources
|
|||
Geometry/Curves.hpp
|
||||
Geometry/MedialAxis.cpp
|
||||
Geometry/MedialAxis.hpp
|
||||
Geometry/Voronoi.cpp
|
||||
Geometry/Voronoi.hpp
|
||||
Geometry/VoronoiOffset.cpp
|
||||
Geometry/VoronoiOffset.hpp
|
||||
Geometry/VoronoiUtils.hpp
|
||||
Geometry/VoronoiUtils.cpp
|
||||
Geometry/VoronoiUtilsCgal.cpp
|
||||
Geometry/VoronoiUtilsCgal.hpp
|
||||
Geometry/VoronoiVisualUtils.hpp
|
||||
|
@ -391,8 +394,6 @@ set(lisbslic3r_sources
|
|||
Arachne/utils/PolygonsSegmentIndex.hpp
|
||||
Arachne/utils/PolylineStitcher.hpp
|
||||
Arachne/utils/PolylineStitcher.cpp
|
||||
Arachne/utils/VoronoiUtils.hpp
|
||||
Arachne/utils/VoronoiUtils.cpp
|
||||
Arachne/SkeletalTrapezoidation.hpp
|
||||
Arachne/SkeletalTrapezoidation.cpp
|
||||
Arachne/SkeletalTrapezoidationEdge.hpp
|
||||
|
|
|
@ -448,7 +448,7 @@ MedialAxis::MedialAxis(double min_width, double max_width, const ExPolygon &expo
|
|||
|
||||
void MedialAxis::build(ThickPolylines* polylines)
|
||||
{
|
||||
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
|
||||
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
|
||||
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_lines);
|
||||
// static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
|
||||
// std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
|
||||
|
|
|
@ -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
|
|
@ -4,8 +4,6 @@
|
|||
#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
|
||||
#pragma warning(push)
|
||||
|
@ -16,18 +14,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_
|
||||
|
|
|
@ -782,9 +782,6 @@ void annotate_inside_outside(VD &vd, const Lines &lines)
|
|||
|
||||
for (const VD::edge_type &edge : vd.edges())
|
||||
if (edge.vertex1() == nullptr) {
|
||||
if (edge.vertex0() == nullptr)
|
||||
continue;
|
||||
|
||||
// Infinite Voronoi edge separating two Point sites or a Point site and a Segment site.
|
||||
// Infinite edge is always outside and it references at least one valid vertex.
|
||||
assert(edge.is_infinite());
|
||||
|
@ -891,9 +888,6 @@ void annotate_inside_outside(VD &vd, const Lines &lines)
|
|||
for (const VD::edge_type &edge : vd.edges()) {
|
||||
assert((edge_category(edge) == EdgeCategory::Unknown) == (edge_category(edge.twin()) == EdgeCategory::Unknown));
|
||||
if (edge_category(edge) == EdgeCategory::Unknown) {
|
||||
if (!edge.is_finite())
|
||||
continue;
|
||||
|
||||
assert(edge.is_finite());
|
||||
const VD::cell_type &cell = *edge.cell();
|
||||
const VD::cell_type &cell2 = *edge.twin()->cell();
|
||||
|
|
|
@ -0,0 +1,283 @@
|
|||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <Arachne/utils/PolygonsSegmentIndex.hpp>
|
||||
#include <MultiMaterialSegmentation.hpp>
|
||||
|
||||
#include "VoronoiUtils.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesIt = ColoredLines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_iterator;
|
||||
|
||||
// Explicit template instantiation.
|
||||
template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
|
||||
template VD::SegmentIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
|
||||
template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
|
||||
template ColoredLinesConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template PolygonsSegmentIndexConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
template Points VoronoiUtils::discretize_parabola(const Point &, const Arachne::PolygonsSegmentIndex &, const Point &, const Point &, coord_t, float);
|
||||
template Arachne::PolygonsPointIndex VoronoiUtils::get_source_point_index(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
typename std::iterator_traits<SegmentIterator>::reference>::type
|
||||
VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
if (!cell.contains_segment())
|
||||
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source segment!");
|
||||
|
||||
if (cell.source_index() >= size_t(std::distance(segment_begin, segment_end)))
|
||||
throw Slic3r::OutOfRange("Voronoi cell source index is out of range!");
|
||||
|
||||
return *(segment_begin + cell.source_index());
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type
|
||||
VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
|
||||
if (!cell.contains_point())
|
||||
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
|
||||
|
||||
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::LOW);
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return boost::polygon::segment_traits<Segment>::get(*segment_it, boost::polygon::HIGH);
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
|
||||
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Function get_source_point() should only be called on point cells!");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
Arachne::PolygonsPointIndex>::type
|
||||
VoronoiUtils::get_source_point_index(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
if (!cell.contains_point())
|
||||
throw Slic3r::InvalidArgument("Voronoi cell doesn't contain a source point!");
|
||||
|
||||
if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return (*segment_it);
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT) {
|
||||
assert(int(cell.source_index()) < std::distance(segment_begin, segment_end));
|
||||
const SegmentIterator segment_it = segment_begin + cell.source_index();
|
||||
return (*segment_it).next();
|
||||
} else if (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT) {
|
||||
throw Slic3r::RuntimeError("Voronoi diagram is always constructed using segments, so cell.source_category() shouldn't be SOURCE_CATEGORY_SINGLE_POINT!");
|
||||
} else {
|
||||
throw Slic3r::InvalidArgument("Function get_source_point_index() should only be called on point cells!");
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Segment>
|
||||
typename boost::polygon::enable_if<typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<Segment>::type>::type>::type,
|
||||
Points>::type
|
||||
VoronoiUtils::discretize_parabola(const Point &source_point, const Segment &source_segment, const Point &start, const Point &end, const coord_t approximate_step_size, float transitioning_angle)
|
||||
{
|
||||
Points discretized;
|
||||
// x is distance of point projected on the segment ab
|
||||
// xx is point projected on the segment ab
|
||||
const Point a = source_segment.from();
|
||||
const Point b = source_segment.to();
|
||||
const Point ab = b - a;
|
||||
const Point as = start - a;
|
||||
const Point ae = end - a;
|
||||
const coord_t ab_size = ab.cast<int64_t>().norm();
|
||||
const coord_t sx = as.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t ex = ae.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
const coord_t sxex = ex - sx;
|
||||
|
||||
const Point ap = source_point - a;
|
||||
const coord_t px = ap.cast<int64_t>().dot(ab.cast<int64_t>()) / ab_size;
|
||||
|
||||
Point pxx;
|
||||
Line(a, b).distance_to_infinite_squared(source_point, &pxx);
|
||||
const Point ppxx = pxx - source_point;
|
||||
const coord_t d = ppxx.cast<int64_t>().norm();
|
||||
|
||||
const Vec2d rot = perp(ppxx).cast<double>().normalized();
|
||||
const double rot_cos_theta = rot.x();
|
||||
const double rot_sin_theta = rot.y();
|
||||
|
||||
if (d == 0) {
|
||||
discretized.emplace_back(start);
|
||||
discretized.emplace_back(end);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
const double marking_bound = atan(transitioning_angle * 0.5);
|
||||
int64_t msx = -marking_bound * int64_t(d); // projected marking_start
|
||||
int64_t mex = marking_bound * int64_t(d); // projected marking_end
|
||||
|
||||
const coord_t marking_start_end_h = msx * msx / (2 * d) + d / 2;
|
||||
Point marking_start = Point(coord_t(msx), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
Point marking_end = Point(coord_t(mex), marking_start_end_h).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
const int dir = (sx > ex) ? -1 : 1;
|
||||
if (dir < 0) {
|
||||
std::swap(marking_start, marking_end);
|
||||
std::swap(msx, mex);
|
||||
}
|
||||
|
||||
bool add_marking_start = msx * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && msx * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
bool add_marking_end = mex * int64_t(dir) > int64_t(sx - px) * int64_t(dir) && mex * int64_t(dir) < int64_t(ex - px) * int64_t(dir);
|
||||
|
||||
const Point apex = Point(0, d / 2).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
bool add_apex = int64_t(sx - px) * int64_t(dir) < 0 && int64_t(ex - px) * int64_t(dir) > 0;
|
||||
|
||||
assert(!add_marking_start || !add_marking_end || add_apex);
|
||||
if (add_marking_start && add_marking_end && !add_apex)
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failing to discretize parabola! Must add an apex or one of the endpoints.";
|
||||
|
||||
const coord_t step_count = lround(static_cast<double>(std::abs(ex - sx)) / approximate_step_size);
|
||||
discretized.emplace_back(start);
|
||||
for (coord_t step = 1; step < step_count; ++step) {
|
||||
const int64_t x = int64_t(sx) + int64_t(sxex) * int64_t(step) / int64_t(step_count) - int64_t(px);
|
||||
const int64_t y = int64_t(x) * int64_t(x) / int64_t(2 * d) + int64_t(d / 2);
|
||||
|
||||
if (add_marking_start && msx * int64_t(dir) < int64_t(x) * int64_t(dir)) {
|
||||
discretized.emplace_back(marking_start);
|
||||
add_marking_start = false;
|
||||
}
|
||||
|
||||
if (add_apex && int64_t(x) * int64_t(dir) > 0) {
|
||||
discretized.emplace_back(apex);
|
||||
add_apex = false; // only add the apex just before the
|
||||
}
|
||||
|
||||
if (add_marking_end && mex * int64_t(dir) < int64_t(x) * int64_t(dir)) {
|
||||
discretized.emplace_back(marking_end);
|
||||
add_marking_end = false;
|
||||
}
|
||||
|
||||
assert(is_in_range<coord_t>(x) && is_in_range<coord_t>(y));
|
||||
const Point result = Point(x, y).rotated(rot_cos_theta, rot_sin_theta) + pxx;
|
||||
discretized.emplace_back(result);
|
||||
}
|
||||
|
||||
if (add_apex)
|
||||
discretized.emplace_back(apex);
|
||||
|
||||
if (add_marking_end)
|
||||
discretized.emplace_back(marking_end);
|
||||
|
||||
discretized.emplace_back(end);
|
||||
return discretized;
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
Geometry::SegmentCellRange<
|
||||
typename boost::polygon::segment_point_type<typename std::iterator_traits<SegmentIterator>::value_type>::type>>::type
|
||||
VoronoiUtils::compute_segment_cell_range(const VD::cell_type &cell, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
using Point = typename boost::polygon::segment_point_type<Segment>::type;
|
||||
using SegmentCellRange = SegmentCellRange<Point>;
|
||||
|
||||
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
|
||||
const Point from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW);
|
||||
const Point to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH);
|
||||
const Vec2i64 from_i64 = from.template cast<int64_t>();
|
||||
const Vec2i64 to_i64 = to.template cast<int64_t>();
|
||||
|
||||
// FIXME @hejllukas: Ensure that there is no infinite edge during iteration between edge_begin and edge_end.
|
||||
SegmentCellRange cell_range(to, from);
|
||||
|
||||
// Find starting edge and end edge
|
||||
bool seen_possible_start = false;
|
||||
bool after_start = false;
|
||||
bool ending_edge_is_set_before_start = false;
|
||||
const VD::edge_type *edge = cell.incident_edge();
|
||||
do {
|
||||
if (edge->is_infinite())
|
||||
continue;
|
||||
|
||||
Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0());
|
||||
Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1());
|
||||
assert(v0 != to_i64 || v1 != from_i64);
|
||||
|
||||
if (v0 == to_i64 && !after_start) { // Use the last edge which starts in source_segment.to
|
||||
cell_range.edge_begin = edge;
|
||||
seen_possible_start = true;
|
||||
} else if (seen_possible_start) {
|
||||
after_start = true;
|
||||
}
|
||||
|
||||
if (v1 == from_i64 && (!cell_range.edge_end || ending_edge_is_set_before_start)) {
|
||||
ending_edge_is_set_before_start = !after_start;
|
||||
cell_range.edge_end = edge;
|
||||
}
|
||||
} while (edge = edge->next(), edge != cell.incident_edge());
|
||||
|
||||
return cell_range;
|
||||
}
|
||||
|
||||
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type *vertex)
|
||||
{
|
||||
assert(vertex != nullptr);
|
||||
return VoronoiUtils::to_point(*vertex);
|
||||
}
|
||||
|
||||
Vec2i64 VoronoiUtils::to_point(const VD::vertex_type &vertex)
|
||||
{
|
||||
const double x = vertex.x(), y = vertex.y();
|
||||
|
||||
assert(std::isfinite(x) && std::isfinite(y));
|
||||
assert(is_in_range<int64_t>(x) && is_in_range<int64_t>(y));
|
||||
|
||||
return {std::llround(x), std::llround(y)};
|
||||
}
|
||||
|
||||
bool VoronoiUtils::is_finite(const VD::vertex_type &vertex)
|
||||
{
|
||||
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
|
||||
}
|
||||
|
||||
VD::vertex_type VoronoiUtils::make_rotated_vertex(VD::vertex_type &vertex, const double angle)
|
||||
{
|
||||
const double cos_a = std::cos(angle);
|
||||
const double sin_a = std::sin(angle);
|
||||
|
||||
const double rotated_x = (cos_a * vertex.x() - sin_a * vertex.y());
|
||||
const double rotated_y = (cos_a * vertex.y() + sin_a * vertex.x());
|
||||
|
||||
VD::vertex_type rotated_vertex{rotated_x, rotated_y};
|
||||
rotated_vertex.incident_edge(vertex.incident_edge());
|
||||
rotated_vertex.color(vertex.color());
|
||||
|
||||
return rotated_vertex;
|
||||
}
|
||||
|
||||
} // namespace Slic3r::Geometry
|
|
@ -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_
|
|
@ -3,7 +3,9 @@
|
|||
#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"
|
||||
|
||||
|
@ -11,18 +13,145 @@ using VD = Slic3r::Geometry::VoronoiDiagram;
|
|||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
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;
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_iterator;
|
||||
|
||||
inline static CGAL_Point to_cgal_point(const VD::vertex_type &pt) { return {pt.x(), pt.y()}; }
|
||||
// Explicit template instantiation.
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt);
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt);
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
|
||||
// The tangent vector of the parabola is computed based on the Proof of the reflective property.
|
||||
// https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property
|
||||
// https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663
|
||||
namespace impl {
|
||||
using K = CGAL::Simple_cartesian<double>;
|
||||
using FK = CGAL::Simple_cartesian<CGAL::Interval_nt_advanced>;
|
||||
using EK = CGAL::Simple_cartesian<CGAL::MP_Float>;
|
||||
using C2E = CGAL::Cartesian_converter<K, EK>;
|
||||
using C2F = CGAL::Cartesian_converter<K, FK>;
|
||||
class Epick : public CGAL::Filtered_kernel_adaptor<CGAL::Type_equality_wrapper<K::Base<Epick>::Type, Epick>, true> {};
|
||||
|
||||
template<typename K>
|
||||
inline typename K::Vector_2 calculate_parabolic_tangent_vector(
|
||||
// Test point on the parabola, where the tangent will be calculated.
|
||||
const typename K::Point_2 &p,
|
||||
// Focus point of the parabola.
|
||||
const typename K::Point_2 &f,
|
||||
// Points of a directrix of the parabola.
|
||||
const typename K::Point_2 &u,
|
||||
const typename K::Point_2 &v,
|
||||
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
|
||||
const typename K::Orientation &tangent_orientation)
|
||||
{
|
||||
using RT = typename K::RT;
|
||||
using Vector_2 = typename K::Vector_2;
|
||||
|
||||
const Vector_2 directrix_vec = v - u;
|
||||
const RT directrix_vec_sqr_length = CGAL::scalar_product(directrix_vec, directrix_vec);
|
||||
Vector_2 focus_vec = (f - u) * directrix_vec_sqr_length - directrix_vec * CGAL::scalar_product(directrix_vec, p - u);
|
||||
Vector_2 tangent_vec = focus_vec.perpendicular(tangent_orientation);
|
||||
return tangent_vec;
|
||||
}
|
||||
|
||||
template<typename K> struct ParabolicTangentToSegmentOrientationPredicate
|
||||
{
|
||||
using Point_2 = typename K::Point_2;
|
||||
using Vector_2 = typename K::Vector_2;
|
||||
using Orientation = typename K::Orientation;
|
||||
using result_type = typename K::Orientation;
|
||||
|
||||
result_type operator()(
|
||||
// Test point on the parabola, where the tangent will be calculated.
|
||||
const Point_2 &p,
|
||||
// End of the linear segment (p, q), for which orientation towards the tangent to parabola will be evaluated.
|
||||
const Point_2 &q,
|
||||
// Focus point of the parabola.
|
||||
const Point_2 &f,
|
||||
// Points of a directrix of the parabola.
|
||||
const Point_2 &u,
|
||||
const Point_2 &v,
|
||||
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
|
||||
const Orientation &tangent_orientation) const
|
||||
{
|
||||
assert(tangent_orientation == CGAL::Orientation::LEFT_TURN || tangent_orientation == CGAL::Orientation::RIGHT_TURN);
|
||||
|
||||
Vector_2 tangent_vec = calculate_parabolic_tangent_vector<K>(p, f, u, v, tangent_orientation);
|
||||
Vector_2 linear_vec = q - p;
|
||||
|
||||
return CGAL::sign(tangent_vec.x() * linear_vec.y() - tangent_vec.y() * linear_vec.x());
|
||||
}
|
||||
};
|
||||
|
||||
template<typename K> struct ParabolicTangentToParabolicTangentOrientationPredicate
|
||||
{
|
||||
using Point_2 = typename K::Point_2;
|
||||
using Vector_2 = typename K::Vector_2;
|
||||
using Orientation = typename K::Orientation;
|
||||
using result_type = typename K::Orientation;
|
||||
|
||||
result_type operator()(
|
||||
// Common point on both parabolas, where the tangent will be calculated.
|
||||
const Point_2 &p,
|
||||
// Focus point of the first parabola.
|
||||
const Point_2 &f_0,
|
||||
// Points of a directrix of the first parabola.
|
||||
const Point_2 &u_0,
|
||||
const Point_2 &v_0,
|
||||
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
|
||||
const Orientation &tangent_orientation_0,
|
||||
// Focus point of the second parabola.
|
||||
const Point_2 &f_1,
|
||||
// Points of a directrix of the second parabola.
|
||||
const Point_2 &u_1,
|
||||
const Point_2 &v_1,
|
||||
// On which side of the parabolic segment endpoints the focus point is, which determines the orientation of the tangent.
|
||||
const Orientation &tangent_orientation_1) const
|
||||
{
|
||||
assert(tangent_orientation_0 == CGAL::Orientation::LEFT_TURN || tangent_orientation_0 == CGAL::Orientation::RIGHT_TURN);
|
||||
assert(tangent_orientation_1 == CGAL::Orientation::LEFT_TURN || tangent_orientation_1 == CGAL::Orientation::RIGHT_TURN);
|
||||
|
||||
Vector_2 tangent_vec_0 = calculate_parabolic_tangent_vector<K>(p, f_0, u_0, v_0, tangent_orientation_0);
|
||||
Vector_2 tangent_vec_1 = calculate_parabolic_tangent_vector<K>(p, f_1, u_1, v_1, tangent_orientation_1);
|
||||
|
||||
return CGAL::sign(tangent_vec_0.x() * tangent_vec_1.y() - tangent_vec_0.y() * tangent_vec_1.x());
|
||||
}
|
||||
};
|
||||
|
||||
using ParabolicTangentToSegmentOrientationPredicateFiltered = CGAL::Filtered_predicate<ParabolicTangentToSegmentOrientationPredicate<EK>, ParabolicTangentToSegmentOrientationPredicate<FK>, C2E, C2F>;
|
||||
using ParabolicTangentToParabolicTangentOrientationPredicateFiltered = CGAL::Filtered_predicate<ParabolicTangentToParabolicTangentOrientationPredicate<EK>, ParabolicTangentToParabolicTangentOrientationPredicate<FK>, C2E, C2F>;
|
||||
} // namespace impl
|
||||
|
||||
using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrientationPredicateFiltered;
|
||||
using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered;
|
||||
using CGAL_Point = impl::K::Point_2;
|
||||
|
||||
inline CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
|
||||
inline CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
|
||||
inline CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
|
||||
|
||||
inline Linef make_linef(const VD::edge_type &edge)
|
||||
{
|
||||
const VD::vertex_type *v0 = edge.vertex0();
|
||||
const VD::vertex_type *v1 = edge.vertex1();
|
||||
return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())};
|
||||
}
|
||||
|
||||
[[maybe_unused]] inline bool is_equal(const VD::vertex_type &vertex_first, const VD::vertex_type &vertex_second) { return vertex_first.x() == vertex_second.x() && vertex_first.y() == vertex_second.y(); }
|
||||
|
||||
// FIXME Lukas H.: Also includes parabolic segments.
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
|
||||
{
|
||||
using CGAL_E_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
|
||||
using CGAL_E_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
|
||||
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_E_Point { return {pt.x(), pt.y()}; };
|
||||
|
||||
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
|
||||
[](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()) {
|
||||
|
@ -30,7 +159,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
|
|||
continue;
|
||||
|
||||
if (edge.is_finite() && edge.is_linear() && edge.vertex0() != nullptr && edge.vertex1() != nullptr &&
|
||||
Arachne::VoronoiUtils::is_finite(*edge.vertex0()) && Arachne::VoronoiUtils::is_finite(*edge.vertex1())) {
|
||||
VoronoiUtils::is_finite(*edge.vertex0()) && VoronoiUtils::is_finite(*edge.vertex1())) {
|
||||
segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1()));
|
||||
edge.color(1);
|
||||
assert(edge.twin() != nullptr);
|
||||
|
@ -41,42 +170,136 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
|
|||
for (const VD::edge_type &edge : voronoi_diagram.edges())
|
||||
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();
|
||||
}
|
||||
|
||||
static bool check_if_three_vectors_are_ccw(const CGAL_Point &common_pt, const CGAL_Point &pt_1, const CGAL_Point &pt_2, const CGAL_Point &test_pt) {
|
||||
CGAL::Orientation orientation = CGAL::orientation(common_pt, pt_1, pt_2);
|
||||
struct ParabolicSegment
|
||||
{
|
||||
const Point focus;
|
||||
const Line directrix;
|
||||
// Two points on the parabola;
|
||||
const Linef segment;
|
||||
// Indicate if focus point is on the left side or right side relative to parabolic segment endpoints.
|
||||
const CGAL::Orientation is_focus_on_left;
|
||||
};
|
||||
|
||||
template<typename SegmentIterator>
|
||||
inline static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
ParabolicSegment>::type
|
||||
get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
assert(edge.is_curved());
|
||||
|
||||
const VD::cell_type *left_cell = edge.cell();
|
||||
const VD::cell_type *right_cell = edge.twin()->cell();
|
||||
|
||||
const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end);
|
||||
const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end);
|
||||
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt)));
|
||||
|
||||
assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN);
|
||||
|
||||
const Point directrix_from = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::LOW);
|
||||
const Point directrix_to = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::HIGH);
|
||||
return {focus_pt, Line(directrix_from, directrix_to), make_linef(edge), focus_side};
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
inline static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
CGAL::Orientation>::type
|
||||
orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0()));
|
||||
CGAL::Orientation orientation;
|
||||
if (edge_a.is_linear() && edge_b.is_linear()) {
|
||||
orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1()));
|
||||
} else if (edge_a.is_curved() && edge_b.is_curved()) {
|
||||
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segment_begin, segment_end);
|
||||
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segment_begin, segment_end);
|
||||
orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a),
|
||||
to_cgal_point(parabolic_a.focus),
|
||||
to_cgal_point(parabolic_a.directrix.a),
|
||||
to_cgal_point(parabolic_a.directrix.b),
|
||||
parabolic_a.is_focus_on_left,
|
||||
to_cgal_point(parabolic_b.focus),
|
||||
to_cgal_point(parabolic_b.directrix.a),
|
||||
to_cgal_point(parabolic_b.directrix.b),
|
||||
parabolic_b.is_focus_on_left);
|
||||
return orientation;
|
||||
} else {
|
||||
assert(edge_a.is_curved() != edge_b.is_curved());
|
||||
|
||||
const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a;
|
||||
const VD::edge_type ¶bolic_edge = edge_a.is_curved() ? edge_a : edge_b;
|
||||
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segment_begin, segment_end);
|
||||
orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()),
|
||||
to_cgal_point(parabolic.focus),
|
||||
to_cgal_point(parabolic.directrix.a),
|
||||
to_cgal_point(parabolic.directrix.b),
|
||||
parabolic.is_focus_on_left);
|
||||
|
||||
if (edge_b.is_curved())
|
||||
orientation = CGAL::opposite(orientation);
|
||||
}
|
||||
|
||||
return orientation;
|
||||
}
|
||||
|
||||
template<typename SegmentIterator>
|
||||
static typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_segment_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
bool>::type
|
||||
check_if_three_edges_are_ccw(const VD::edge_type &edge_first,
|
||||
const VD::edge_type &edge_second,
|
||||
const VD::edge_type &edge_third,
|
||||
const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end)
|
||||
{
|
||||
assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0()));
|
||||
|
||||
CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end);
|
||||
if (orientation == CGAL::Orientation::COLLINEAR) {
|
||||
// The first two edges are collinear, so the third edge must be on the right side on the first of them.
|
||||
return CGAL::orientation(common_pt, pt_1, test_pt) == CGAL::Orientation::RIGHT_TURN;
|
||||
return orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end) == CGAL::Orientation::RIGHT_TURN;
|
||||
} else if (orientation == CGAL::Orientation::LEFT_TURN) {
|
||||
// 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 = CGAL::orientation(common_pt, pt_1, test_pt);
|
||||
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
|
||||
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 = CGAL::orientation(common_pt, pt_1, test_pt);
|
||||
CGAL::Orientation orientation2 = CGAL::orientation(common_pt, pt_2, test_pt);
|
||||
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)
|
||||
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 {
|
||||
// FIXME Lukas H.: Also process parabolic segments.
|
||||
if (edge->is_finite() && edge->is_linear() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
|
||||
Arachne::VoronoiUtils::is_finite(*edge->vertex0()) && Arachne::VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
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();
|
||||
|
@ -85,12 +308,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_vectors_are_ccw(to_cgal_point(*prev_edge->vertex0()), to_cgal_point(*prev_edge->vertex1()),
|
||||
to_cgal_point(*curr_edge->vertex1()), to_cgal_point(*next_edge->vertex1())))
|
||||
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -99,5 +321,4 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace Slic3r::Geometry
|
|
@ -2,6 +2,7 @@
|
|||
#define slic3r_VoronoiUtilsCgal_hpp_
|
||||
|
||||
#include "Voronoi.hpp"
|
||||
#include "../Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
class VoronoiDiagram;
|
||||
|
@ -13,8 +14,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);
|
||||
|
||||
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
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -6,13 +6,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_
|
||||
|
|
Loading…
Reference in New Issue