Generalize all functions in VoronoiUtils to not depend on Arachne data structures.

Also, move VoronoiUtils from Arachne namespace to Geometry namespace.
This commit is contained in:
Lukáš Hejl 2024-01-31 17:41:45 +01:00
parent 23b7c41857
commit ac33876796
9 changed files with 393 additions and 376 deletions

View File

@ -10,8 +10,6 @@
#include <functional> #include <functional>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include "utils/VoronoiUtils.hpp"
#include "utils/linearAlg2D.hpp" #include "utils/linearAlg2D.hpp"
#include "Utils.hpp" #include "Utils.hpp"
#include "SVG.hpp" #include "SVG.hpp"
@ -19,6 +17,8 @@
#include "Geometry/VoronoiUtilsCgal.hpp" #include "Geometry/VoronoiUtilsCgal.hpp"
#include "../EdgeGrid.hpp" #include "../EdgeGrid.hpp"
#include "Geometry/VoronoiUtils.hpp"
#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance). #define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance).
namespace Slic3r::Arachne namespace Slic3r::Arachne
@ -218,20 +218,17 @@ void SkeletalTrapezoidation::transferEdge(Point from, Point to, VD::edge_type& v
Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments) Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments)
{ {
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(vd_edge));
/*Terminology in this function assumes that the edge moves horizontally from /*Terminology in this function assumes that the edge moves horizontally from
left to right. This is not necessarily the case; the edge can go in any left to right. This is not necessarily the case; the edge can go in any
direction, but it helps to picture it in a certain direction in your head.*/ direction, but it helps to picture it in a certain direction in your head.*/
const VD::cell_type* left_cell = vd_edge.cell(); const VD::cell_type *left_cell = vd_edge.cell();
const VD::cell_type* right_cell = vd_edge.twin()->cell(); const VD::cell_type *right_cell = vd_edge.twin()->cell();
assert(VoronoiUtils::p(vd_edge.vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge.vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex0()).y() >= std::numeric_limits<coord_t>::lowest()); Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge.vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(vd_edge.vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge.vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
Point start = VoronoiUtils::p(vd_edge.vertex0()).cast<coord_t>();
Point end = VoronoiUtils::p(vd_edge.vertex1()).cast<coord_t>();
bool point_left = left_cell->contains_point(); bool point_left = left_cell->contains_point();
bool point_right = right_cell->contains_point(); bool point_right = right_cell->contains_point();
@ -241,20 +238,20 @@ Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const st
} }
else if (point_left != point_right) //This is a parabolic edge between a point and a line. else if (point_left != point_right) //This is a parabolic edge between a point and a line.
{ {
Point p = VoronoiUtils::getSourcePoint(*(point_left ? left_cell : right_cell), segments); Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end());
const Segment& s = VoronoiUtils::getSourceSegment(*(point_left ? right_cell : left_cell), segments); const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end());
return VoronoiUtils::discretizeParabola(p, s, start, end, discretization_step_size, transitioning_angle); return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle);
} }
else //This is a straight edge between two points. else //This is a straight edge between two points.
{ {
/*While the edge is straight, it is still discretized since the part /*While the edge is straight, it is still discretized since the part
becomes narrower between the two points. As such it may need different becomes narrower between the two points. As such it may need different
beadings along the way.*/ beadings along the way.*/
Point left_point = VoronoiUtils::getSourcePoint(*left_cell, segments); Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end());
Point right_point = VoronoiUtils::getSourcePoint(*right_cell, segments); Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end());
coord_t d = (right_point - left_point).cast<int64_t>().norm(); coord_t d = (right_point - left_point).cast<int64_t>().norm();
Point middle = (left_point + right_point) / 2; Point middle = (left_point + right_point) / 2;
Point x_axis_dir = perp(Point(right_point - left_point)); Point x_axis_dir = perp(Point(right_point - left_point));
coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm(); coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm();
const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge. const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge.
@ -345,11 +342,11 @@ bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& s
vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest())) vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest()))
return false; // Don't copy any part of this cell return false; // Don't copy any part of this cell
const Point source_point = VoronoiUtils::getSourcePoint(cell, segments); const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end());
const PolygonsPointIndex source_point_index = VoronoiUtils::getSourcePointIndex(cell, segments); const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end());
Vec2i64 some_point = VoronoiUtils::p(cell.incident_edge()->vertex0()); Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0());
if (some_point == source_point.cast<int64_t>()) if (some_point == source_point.cast<int64_t>())
some_point = VoronoiUtils::p(cell.incident_edge()->vertex1()); some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1());
//Test if the some_point is even inside the polygon. //Test if the some_point is even inside the polygon.
//The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex. //The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex.
@ -361,13 +358,13 @@ bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& s
VD::edge_type* vd_edge = cell.incident_edge(); VD::edge_type* vd_edge = cell.incident_edge();
do { do {
assert(vd_edge->is_finite()); assert(vd_edge->is_finite());
if (Vec2i64 p1 = VoronoiUtils::p(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) { if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) {
start_source_point = source_point; start_source_point = source_point;
end_source_point = source_point; end_source_point = source_point;
starting_vd_edge = vd_edge->next(); starting_vd_edge = vd_edge->next();
ending_vd_edge = vd_edge; ending_vd_edge = vd_edge;
} else { } else {
assert((VoronoiUtils::p(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input."); assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input.");
} }
} }
while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge()); while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge());
@ -376,47 +373,6 @@ bool SkeletalTrapezoidation::computePointCellRange(VD::cell_type& cell, Point& s
return true; return true;
} }
void SkeletalTrapezoidation::computeSegmentCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::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::edge_type* edge = cell.incident_edge();
do {
if (edge->is_infinite())
continue;
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0());
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1());
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>() ));
if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
starting_vd_edge = edge;
seen_possible_start = true;
}
else if (seen_possible_start) {
after_start = true;
}
if (v1 == from.cast<int64_t>() && (!ending_vd_edge || ending_edge_is_set_before_start)) {
ending_edge_is_set_before_start = !after_start;
ending_vd_edge = edge;
}
} while (edge = edge->next(), edge != cell.incident_edge());
assert(starting_vd_edge && ending_vd_edge);
assert(starting_vd_edge != ending_vd_edge);
start_source_point = source_segment.to();
end_source_point = source_segment.from();
}
SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy, SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy,
double transitioning_angle, coord_t discretization_step_size, double transitioning_angle, coord_t discretization_step_size,
coord_t transition_filter_dist, coord_t allowed_filter_deviation, coord_t transition_filter_dist, coord_t allowed_filter_deviation,
@ -433,11 +389,11 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
static bool has_finite_edge_with_non_finite_vertex(const VD &voronoi_diagram) static bool has_finite_edge_with_non_finite_vertex(const VD &voronoi_diagram)
{ {
for (const VoronoiUtils::vd_t::edge_type &edge : voronoi_diagram.edges()) { for (const VD::edge_type &edge : voronoi_diagram.edges()) {
if (edge.is_finite()) { if (edge.is_finite()) {
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr); assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !Geometry::VoronoiUtils::is_finite(*edge.vertex0()) ||
!VoronoiUtils::is_finite(*edge.vertex1())) !Geometry::VoronoiUtils::is_finite(*edge.vertex1()))
return true; return true;
} }
} }
@ -453,7 +409,7 @@ static bool detect_missing_voronoi_vertex(const VD &voronoi_diagram, const std::
continue; // There is no spoon continue; // There is no spoon
if (cell.contains_segment()) { if (cell.contains_segment()) {
const SkeletalTrapezoidation::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); const SkeletalTrapezoidation::Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segments.begin(), segments.end());
const Point from = source_segment.from(); const Point from = source_segment.from();
const Point to = source_segment.to(); const Point to = source_segment.to();
@ -462,15 +418,15 @@ static bool detect_missing_voronoi_vertex(const VD &voronoi_diagram, const std::
bool seen_possible_start = false; bool seen_possible_start = false;
bool after_start = false; bool after_start = false;
bool ending_edge_is_set_before_start = false; bool ending_edge_is_set_before_start = false;
VoronoiUtils::vd_t::edge_type *starting_vd_edge = nullptr; VD::edge_type *starting_vd_edge = nullptr;
VoronoiUtils::vd_t::edge_type *ending_vd_edge = nullptr; VD::edge_type *ending_vd_edge = nullptr;
VoronoiUtils::vd_t::edge_type *edge = cell.incident_edge(); VD::edge_type *edge = cell.incident_edge();
do { do {
if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !VoronoiUtils::is_finite(*edge->vertex0()) || !VoronoiUtils::is_finite(*edge->vertex1())) if (edge->is_infinite() || edge->vertex0() == nullptr || edge->vertex1() == nullptr || !Geometry::VoronoiUtils::is_finite(*edge->vertex0()) || !Geometry::VoronoiUtils::is_finite(*edge->vertex1()))
continue; continue;
Vec2i64 v0 = VoronoiUtils::p(edge->vertex0()); Vec2i64 v0 = Geometry::VoronoiUtils::to_point(edge->vertex0());
Vec2i64 v1 = VoronoiUtils::p(edge->vertex1()); Vec2i64 v1 = Geometry::VoronoiUtils::to_point(edge->vertex1());
assert(!(v0 == to.cast<int64_t>() && v1 == from.cast<int64_t>())); 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 if (v0 == to.cast<int64_t>() && !after_start) { // Use the last edge which starts in source_segment.to
@ -517,7 +473,7 @@ inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalT
} }
} }
bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments) bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<PolygonsSegmentIndex> &segments)
{ {
for (VD::cell_type cell : voronoi_diagram.cells()) { for (VD::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge()) if (!cell.incident_edge())
@ -526,18 +482,16 @@ bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, c
if (!cell.contains_segment()) if (!cell.contains_segment())
continue; // Skip cells that don't contain segments. continue; // Skip cells that don't contain segments.
const VoronoiUtils::Segment &source_segment = VoronoiUtils::getSourceSegment(cell, segments); const PolygonsSegmentIndex &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segments.begin(), segments.end());
const Vec2d source_segment_from = source_segment.from().cast<double>(); const Vec2d source_segment_from = source_segment.from().cast<double>();
const Vec2d source_segment_vec = source_segment.to().cast<double>() - source_segment_from; const Vec2d source_segment_vec = source_segment.to().cast<double>() - source_segment_from;
Point start_source_point, end_source_point; Geometry::SegmentCellRange<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.begin(), segments.end());
VD::edge_type *begin_voronoi_edge = nullptr, *end_voronoi_edge = nullptr;
SkeletalTrapezoidation::computeSegmentCellRange(cell, start_source_point, end_source_point, begin_voronoi_edge, end_voronoi_edge, segments);
// All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid. // All Voronoi vertices must be on left side of the source segment, otherwise Voronoi diagram is invalid.
// FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases. // FIXME Lukas H.: Be aware that begin_voronoi_edge and end_voronoi_edge could be nullptr in some specific cases.
// It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf). // It mostly happens when there is some missing Voronoi, for example, in GH issue #8846 (IssuesWithMysteriousPerimeters.3mf).
if (begin_voronoi_edge != nullptr && end_voronoi_edge != nullptr) if (cell_range.edge_begin != nullptr && cell_range.edge_end != nullptr)
for (VD::edge_type *edge = begin_voronoi_edge; edge != end_voronoi_edge; edge = edge->next()) for (VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next())
if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0)
return true; return true;
} }
@ -652,7 +606,7 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
#endif #endif
VD voronoi_diagram; VD voronoi_diagram;
voronoi_diagram.construct_voronoi(segments.begin(), segments.end()); voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
#ifdef ARACHNE_DEBUG_VORONOI #ifdef ARACHNE_DEBUG_VORONOI
{ {
@ -696,10 +650,10 @@ process_voronoi_diagram:
if (!cell.incident_edge()) if (!cell.incident_edge())
continue; // There is no spoon continue; // There is no spoon
Point start_source_point; Point start_source_point;
Point end_source_point; Point end_source_point;
VD::edge_type* starting_voronoi_edge = nullptr; VD::edge_type *starting_voronoi_edge = nullptr;
VD::edge_type* ending_voronoi_edge = nullptr; VD::edge_type *ending_voronoi_edge = nullptr;
// Compute and store result in above variables // Compute and store result in above variables
if (cell.contains_point()) { if (cell.contains_point()) {
@ -708,7 +662,12 @@ process_voronoi_diagram:
continue; continue;
} else { } else {
assert(cell.contains_segment()); assert(cell.contains_segment());
computeSegmentCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments); Geometry::SegmentCellRange<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend());
assert(cell_range.is_valid());
start_source_point = cell_range.segment_start_point;
end_source_point = cell_range.segment_end_point;
starting_voronoi_edge = cell_range.edge_begin;
ending_voronoi_edge = cell_range.edge_end;
} }
if (!starting_voronoi_edge || !ending_voronoi_edge) { if (!starting_voronoi_edge || !ending_voronoi_edge) {
@ -717,33 +676,25 @@ process_voronoi_diagram:
} }
// Copy start to end edge to graph // Copy start to end edge to graph
edge_t* prev_edge = nullptr; assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*starting_voronoi_edge));
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest()); edge_t *prev_edge = nullptr;
assert(VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest()); transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
transferEdge(start_source_point, VoronoiUtils::p(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments); node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
node_t* starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
starting_node->data.distance_to_boundary = 0; starting_node->data.distance_to_boundary = 0;
constexpr bool is_next_to_start_or_end = true; constexpr bool is_next_to_start_or_end = true;
graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end); graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end);
for (VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) { for (VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
assert(vd_edge->is_finite()); assert(vd_edge->is_finite());
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
assert(VoronoiUtils::p(vd_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest()); Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
assert(VoronoiUtils::p(vd_edge->vertex1()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).x() >= std::numeric_limits<coord_t>::lowest());
assert(VoronoiUtils::p(vd_edge->vertex1()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(vd_edge->vertex1()).y() >= std::numeric_limits<coord_t>::lowest());
Point v1 = VoronoiUtils::p(vd_edge->vertex0()).cast<coord_t>();
Point v2 = VoronoiUtils::p(vd_edge->vertex1()).cast<coord_t>();
transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments); transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments);
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge); graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
} }
assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).x() >= std::numeric_limits<coord_t>::lowest()); transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
assert(VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() <= std::numeric_limits<coord_t>::max() && VoronoiUtils::p(starting_voronoi_edge->vertex0()).y() >= std::numeric_limits<coord_t>::lowest());
transferEdge(VoronoiUtils::p(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
prev_edge->to->data.distance_to_boundary = 0; prev_edge->to->data.distance_to_boundary = 0;
} }

View File

@ -11,8 +11,6 @@
#include <ankerl/unordered_dense.h> #include <ankerl/unordered_dense.h>
#include <Arachne/utils/VoronoiUtils.hpp>
#include "utils/HalfEdgeGraph.hpp" #include "utils/HalfEdgeGraph.hpp"
#include "utils/PolygonsSegmentIndex.hpp" #include "utils/PolygonsSegmentIndex.hpp"
#include "utils/ExtrusionJunction.hpp" #include "utils/ExtrusionJunction.hpp"
@ -235,32 +233,6 @@ protected:
*/ */
static bool computePointCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector<Segment>& segments); static bool computePointCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, VD::edge_type*& ending_vd_edge, const std::vector<Segment>& segments);
/*!
* Compute the range of line segments that surround a cell of the skeletal
* graph that belongs to a line segment of the medial axis.
*
* This should only be used on cells that belong to a central line segment
* of the skeletal graph, e.g. trapezoid cells, not triangular cells.
*
* The resulting line segments is just the first and the last segment. They
* are linked to the neighboring segments, so you can iterate over the
* segments until you reach the last segment.
* \param cell The cell to compute the range of line segments for.
* \param[out] start_source_point The start point of the source segment of
* this cell.
* \param[out] end_source_point The end point of the source segment of this
* cell.
* \param[out] starting_vd_edge The edge of the Voronoi diagram where the
* loop around the cell starts.
* \param[out] ending_vd_edge The edge of the Voronoi diagram where the loop
* around the cell ends.
* \param points All vertices of the input Polygons.
* \param segments All edges of the input Polygons.
* /return Whether the cell is inside of the polygon. If it's outside of the
* polygon we should skip processing it altogether.
*/
static void computeSegmentCellRange(VD::cell_type& cell, Point& start_source_point, Point& end_source_point, VD::edge_type*& starting_vd_edge, 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 * 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
* That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes * That way we can reach both the quad_start and the quad_end from the [incident_edge] of the two new nodes
@ -602,7 +574,7 @@ protected:
*/ */
void generateLocalMaximaSingleBeads(); void generateLocalMaximaSingleBeads();
friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments); friend bool detect_voronoi_edge_intersecting_input_segment(const VD &voronoi_diagram, const std::vector<Segment> &segments);
}; };
} // namespace Slic3r::Arachne } // namespace Slic3r::Arachne

View File

@ -1,181 +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"
#include "libslic3r/Geometry/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 boost::polygon::segment_traits<Segment>::get(segments[cell.source_index()], boost::polygon::LOW);
break;
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT:
assert(cell.source_index() < segments.size());
return boost::polygon::segment_traits<Segment>::get(segments[cell.source_index()], boost::polygon::HIGH);
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());
return segments[cell.source_index()];
break;
}
case boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT: {
assert(cell.source_index() < segments.size());
return segments[cell.source_index()].next();
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()];
}
Points VoronoiUtils::discretizeParabola(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(Geometry::VoronoiUtils::is_in_range<coord_t>(x) && Geometry::VoronoiUtils::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;
}
}//namespace Slic3r::Arachne

View File

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

View File

@ -496,8 +496,6 @@ set(SLIC3R_SOURCES
Arachne/utils/PolygonsSegmentIndex.hpp Arachne/utils/PolygonsSegmentIndex.hpp
Arachne/utils/PolylineStitcher.hpp Arachne/utils/PolylineStitcher.hpp
Arachne/utils/PolylineStitcher.cpp Arachne/utils/PolylineStitcher.cpp
Arachne/utils/VoronoiUtils.hpp
Arachne/utils/VoronoiUtils.cpp
Arachne/SkeletalTrapezoidation.hpp Arachne/SkeletalTrapezoidation.hpp
Arachne/SkeletalTrapezoidation.cpp Arachne/SkeletalTrapezoidation.cpp
Arachne/SkeletalTrapezoidationEdge.hpp Arachne/SkeletalTrapezoidationEdge.hpp

View File

@ -1,7 +1,251 @@
#include <Arachne/utils/PolygonsSegmentIndex.hpp>
#include <MultiMaterialSegmentation.hpp>
#include "VoronoiUtils.hpp" #include "VoronoiUtils.hpp"
namespace Slic3r::Geometry { namespace Slic3r::Geometry {
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
using LinesIt = Lines::iterator;
using ColoredLinesIt = ColoredLines::iterator;
// Explicit template instantiation.
template LinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template ColoredLinesIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
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 &, ColoredLinesIt, ColoredLinesIt);
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, LinesIt, LinesIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(VoronoiDiagram::cell_type &, ColoredLinesIt, ColoredLinesIt);
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(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(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;
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)
{
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) bool VoronoiUtils::is_finite(const VD::vertex_type &vertex)
{ {
return std::isfinite(vertex.x()) && std::isfinite(vertex.y()); return std::isfinite(vertex.x()) && std::isfinite(vertex.y());

View File

@ -2,16 +2,96 @@
#define slic3r_VoronoiUtils_hpp_ #define slic3r_VoronoiUtils_hpp_
#include "libslic3r/Geometry/Voronoi.hpp" #include "libslic3r/Geometry/Voronoi.hpp"
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
using VD = Slic3r::Geometry::VoronoiDiagram; using VD = Slic3r::Geometry::VoronoiDiagram;
namespace Slic3r::Geometry { 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.
VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
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 class VoronoiUtils
{ {
public: public:
static Vec2i64 to_point(const VD::vertex_type *vertex);
static bool is_finite(const VD::vertex_type &vertex); static bool is_finite(const VD::vertex_type &vertex);
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(VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
template<typename T> static bool is_in_range(double value) 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()); return double(std::numeric_limits<T>::lowest()) <= value && value <= double(std::numeric_limits<T>::max());

View File

@ -7,7 +7,7 @@
#include <CGAL/Surface_sweep_2_algorithms.h> #include <CGAL/Surface_sweep_2_algorithms.h>
#include "libslic3r/Geometry/Voronoi.hpp" #include "libslic3r/Geometry/Voronoi.hpp"
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp" #include "libslic3r/Geometry/VoronoiUtils.hpp"
#include "VoronoiUtilsCgal.hpp" #include "VoronoiUtilsCgal.hpp"
@ -178,22 +178,22 @@ struct ParabolicSegment
const CGAL::Orientation is_focus_on_left; const CGAL::Orientation is_focus_on_left;
}; };
inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector<VoronoiUtils::Segment> &segments) inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector<PolygonsSegmentIndex> &segments)
{ {
assert(edge.is_curved()); assert(edge.is_curved());
const VD::cell_type *left_cell = edge.cell(); const VD::cell_type *left_cell = edge.cell();
const VD::cell_type *right_cell = edge.twin()->cell(); const VD::cell_type *right_cell = edge.twin()->cell();
const Point focus_pt = VoronoiUtils::getSourcePoint(*(left_cell->contains_point() ? left_cell : right_cell), segments); const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segments.begin(), segments.end());
const VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments); const PolygonsSegmentIndex &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segments.begin(), segments.end());
CGAL::Orientation focus_side = CGAL::opposite(CGAL::orientation(to_cgal_point(edge.vertex0()), to_cgal_point(edge.vertex1()), to_cgal_point(focus_pt))); 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); assert(focus_side == CGAL::Orientation::LEFT_TURN || focus_side == CGAL::Orientation::RIGHT_TURN);
return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side}; return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side};
} }
inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector<VoronoiUtils::Segment> &segments) { inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const std::vector<PolygonsSegmentIndex> &segments) {
assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0())); assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0()));
CGAL::Orientation orientation; CGAL::Orientation orientation;
if (edge_a.is_linear() && edge_b.is_linear()) { if (edge_a.is_linear() && edge_b.is_linear()) {
@ -230,7 +230,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
return orientation; return orientation;
} }
static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector<VoronoiUtils::Segment> &segments) static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::edge_type &second, const VD::edge_type &third, const std::vector<PolygonsSegmentIndex> &segments)
{ {
assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0())); assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0()));
@ -254,7 +254,7 @@ static bool check_if_three_edges_are_ccw(const VD::edge_type &first, const VD::e
} }
} }
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments) bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram, const std::vector<PolygonsSegmentIndex> &segments)
{ {
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) { for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
std::vector<const VD::edge_type *> edges; std::vector<const VD::edge_type *> edges;

View File

@ -6,7 +6,7 @@
#define slic3r_VoronoiUtilsCgal_hpp_ #define slic3r_VoronoiUtilsCgal_hpp_
#include "Voronoi.hpp" #include "Voronoi.hpp"
#include "../Arachne/utils/VoronoiUtils.hpp" #include "../Arachne/utils/PolygonsSegmentIndex.hpp"
namespace Slic3r::Geometry { namespace Slic3r::Geometry {
class VoronoiDiagram; class VoronoiDiagram;
@ -18,7 +18,7 @@ public:
static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram); static bool is_voronoi_diagram_planar_intersection(const VoronoiDiagram &voronoi_diagram);
// Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex. // Check if the Voronoi diagram is planar using verification that all neighboring edges are ordered CCW for each vertex.
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<Arachne::VoronoiUtils::Segment> &segments); static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<Arachne::PolygonsSegmentIndex> &segments);
}; };
} // namespace Slic3r::Geometry } // namespace Slic3r::Geometry