Generalize fixing of invalid Voronoi diagram by rotating back and forth.

Now, this fixing operation can be applied to any Voronoi diagram constructed from segments.
This commit is contained in:
Lukáš Hejl 2024-01-31 17:43:01 +01:00
parent 227e82a6ba
commit a358f13de2
7 changed files with 224 additions and 100 deletions

View File

@ -387,71 +387,6 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
constructFromPolygons(polys);
}
using PointMap = SkeletalTrapezoidation::PointMap;
inline static void rotate_back_skeletal_trapezoidation_graph_after_fix(SkeletalTrapezoidationGraph &graph,
const double fix_angle,
const PointMap &vertex_mapping)
{
for (STHalfEdgeNode &node : graph.nodes) {
// If a mapping exists between a rotated point and an original point, use this mapping. Otherwise, rotate a point in the opposite direction.
if (auto node_it = vertex_mapping.find(node.p); node_it != vertex_mapping.end())
node.p = node_it->second;
else
node.p.rotate(-fix_angle);
}
}
inline static std::pair<PointMap, double> try_to_fix_degenerated_voronoi_diagram_by_rotation(
VD &voronoi_diagram,
const Polygons &polys,
Polygons &polys_rotated,
std::vector<SkeletalTrapezoidation::Segment> &segments,
const std::vector<double> &fix_angles)
{
const Polygons polys_rotated_original = polys_rotated;
double fixed_by_angle = fix_angles.front();
PointMap vertex_mapping;
for (const double &fix_angle : fix_angles) {
vertex_mapping.clear();
polys_rotated = polys_rotated_original;
fixed_by_angle = fix_angle;
for (Polygon &poly : polys_rotated)
poly.rotate(fix_angle);
assert(polys_rotated.size() == polys.size());
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
assert(polys_rotated[poly_idx].size() == polys[poly_idx].size());
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); ++point_idx)
vertex_mapping.insert({polys_rotated[poly_idx][point_idx], polys[poly_idx][point_idx]});
}
segments.clear();
for (size_t poly_idx = 0; poly_idx < polys_rotated.size(); poly_idx++)
for (size_t point_idx = 0; point_idx < polys_rotated[poly_idx].size(); point_idx++)
segments.emplace_back(&polys_rotated, poly_idx, point_idx);
voronoi_diagram.clear();
voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
#ifdef ARACHNE_DEBUG_VORONOI
{
static int iRun = 0;
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-rotated-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
}
#endif
if (VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend()) == VD::IssueType::NO_ISSUE_DETECTED)
break;
}
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
return {vertex_mapping, fixed_by_angle};
}
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
{
#ifdef ARACHNE_DEBUG
@ -493,35 +428,6 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
}
#endif
// When any Voronoi vertex is missing, the Voronoi diagram is not planar, or some voronoi edge is
// intersecting input segment, rotate the input polygon and try again.
VD::IssueType status = VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend());
const std::vector<double> fix_angles = {PI / 6, PI / 5, PI / 7, PI / 11};
double fixed_by_angle = fix_angles.front();
PointMap vertex_mapping;
// polys_copy is referenced through items stored in the std::vector segments.
Polygons polys_copy = polys;
if (status != VD::IssueType::NO_ISSUE_DETECTED) {
if (status == VD::IssueType::MISSING_VORONOI_VERTEX)
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
else if (status == VD::IssueType::NON_PLANAR_VORONOI_DIAGRAM)
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
else if (status == VD::IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
std::tie(vertex_mapping, fixed_by_angle) = try_to_fix_degenerated_voronoi_diagram_by_rotation(voronoi_diagram, polys, polys_copy, segments, fix_angles);
VD::IssueType status_after_fix = VD::detect_known_issues(voronoi_diagram, segments.cbegin(), segments.cend());
assert(status_after_fix == VD::IssueType::NO_ISSUE_DETECTED);
if (status_after_fix == VD::IssueType::MISSING_VORONOI_VERTEX)
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
else if (status_after_fix == VD::IssueType::NON_PLANAR_VORONOI_DIAGRAM)
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
else if (status_after_fix == VD::IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT)
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
}
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::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
@ -575,9 +481,6 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
prev_edge->to->data.distance_to_boundary = 0;
}
if (status != VD::IssueType::NO_ISSUE_DETECTED)
rotate_back_skeletal_trapezoidation_graph_after_fix(this->graph, fixed_by_angle, vertex_mapping);
#ifdef ARACHNE_DEBUG
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
#endif

View File

@ -80,7 +80,6 @@ class SkeletalTrapezoidation
public:
using Segment = PolygonsSegmentIndex;
using PointMap = ankerl::unordered_dense::map<Point, Point, PointHash>;
using NodeSet = ankerl::unordered_dense::set<node_t*>;
/*!

View File

@ -81,6 +81,74 @@ void VoronoiDiagram::clear()
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<
@ -166,7 +234,39 @@ typename boost::polygon::enable_if<
VoronoiDiagram::IssueType>::type
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end)
{
return this->m_issue_type;
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>
@ -178,7 +278,77 @@ VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const Segm
const SegmentIterator segment_end,
const double fix_angle)
{
return this->m_issue_type;
using SegmentType = typename std::iterator_traits<SegmentIterator>::value_type;
using PointType = typename boost::polygon::segment_traits<SegmentType>::point_type;
// Copy all segments and rotate their vertices.
std::vector<VoronoiDiagram::Segment> segments_rotated;
segments_rotated.reserve(std::distance(segment_begin, segment_end));
for (auto segment_it = segment_begin; segment_it != segment_end; ++segment_it) {
PointType from = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::LOW);
PointType to = boost::polygon::segment_traits<SegmentType>::get(*segment_it, boost::polygon::HIGH);
segments_rotated.emplace_back(from.rotated(fix_angle), to.rotated(fix_angle));
}
VoronoiDiagram::voronoi_diagram_type voronoi_diagram_rotated;
boost::polygon::construct_voronoi(segments_rotated.begin(), segments_rotated.end(), &voronoi_diagram_rotated);
this->copy_to_local(voronoi_diagram_rotated);
const IssueType issue_type = detect_known_issues(*this, segments_rotated.begin(), segments_rotated.end());
// We want to remap all Voronoi vertices at the endpoints of input segments
// to ensure that Voronoi vertices at endpoints will be preserved after rotation.
// So we assign every Voronoi vertices color to map this Vertex into input segments.
for (cell_type cell : m_cells) {
if (cell.is_degenerate())
continue;
if (cell.contains_segment()) {
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segments_rotated.begin(), segments_rotated.end()); cell_range.is_valid()) {
if (cell_range.edge_end->vertex1()->color() == 0) {
// Vertex 1 of edge_end points to the starting endpoint of the input segment (from() or line.a).
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::LOW);
cell_range.edge_end->vertex1()->color(color);
}
if (cell_range.edge_begin->vertex0()->color() == 0) {
// Vertex 0 of edge_end points to the ending endpoint of the input segment (to() or line.b).
VD::vertex_type::color_type color = encode_input_segment_endpoint(cell.source_index(), boost::polygon::HIGH);
cell_range.edge_begin->vertex0()->color(color);
}
} else {
// This could happen when there is a missing Voronoi vertex even after rotation.
assert(cell_range.is_valid());
}
}
// FIXME @hejllukas: Implement mapping also for source points and not just for source segments.
}
// Rotate all Voronoi vertices back.
// When a Voronoi vertex can be mapped to the input segment endpoint, then we don't need to do rotation back.
for (vertex_type &vertex : m_vertices) {
if (vertex.color() == 0) {
// This vertex isn't mapped to any vertex, so we rotate it back.
vertex = VoronoiUtils::make_rotated_vertex(vertex, -fix_angle);
} else {
// This vertex can be mapped to the input segment endpoint.
PointType endpoint = decode_input_segment_endpoint(vertex.color(), segment_begin, segment_end);
vertex_type endpoint_vertex{double(endpoint.x()), double(endpoint.y())};
endpoint_vertex.incident_edge(vertex.incident_edge());
endpoint_vertex.color(vertex.color());
vertex = endpoint_vertex;
}
}
// We have to clear all marked vertices because some algorithms expect that all vertices have a color equal to 0.
for (vertex_type &vertex : m_vertices)
vertex.color(0);
m_voronoi_diagram.clear();
m_is_modified = true;
return issue_type;
}
} // namespace Slic3r::Geometry

View File

@ -140,6 +140,16 @@ public:
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
@ -161,8 +171,29 @@ private:
bool m_is_modified = false;
State m_state = State::UNKNOWN;
IssueType m_issue_type = IssueType::UNKNOWN;
public:
using SegmentIt = std::vector<Slic3r::Geometry::VoronoiDiagram::Segment>::iterator;
friend struct boost::polygon::segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>;
};
} // namespace Slic3r::Geometry
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Geometry::VoronoiDiagram::Segment>
{
typedef segment_concept type;
};
template<> struct segment_traits<Slic3r::Geometry::VoronoiDiagram::Segment>
{
using coordinate_type = coord_t;
using point_type = Slic3r::Point;
using segment_type = Slic3r::Geometry::VoronoiDiagram::Segment;
static inline point_type get(const segment_type &segment, direction_1d dir) { return dir.to_int() ? segment.to : segment.from; }
};
} // namespace boost::polygon
#endif // slic3r_Geometry_Voronoi_hpp_

View File

@ -11,12 +11,15 @@ using ColoredLinesIt = ColoredLines::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 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 &, 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 &, VD::SegmentIt, VD::SegmentIt);
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);
@ -251,4 +254,19 @@ bool VoronoiUtils::is_finite(const VD::vertex_type &vertex)
return std::isfinite(vertex.x()) && std::isfinite(vertex.y());
}
VD::vertex_type VoronoiUtils::make_rotated_vertex(VD::vertex_type &vertex, const double angle)
{
const double cos_a = std::cos(angle);
const double sin_a = std::sin(angle);
const double rotated_x = (cos_a * vertex.x() - sin_a * vertex.y());
const double rotated_y = (cos_a * vertex.y() + sin_a * vertex.x());
VD::vertex_type rotated_vertex{rotated_x, rotated_y};
rotated_vertex.incident_edge(vertex.incident_edge());
rotated_vertex.color(vertex.color());
return rotated_vertex;
}
} // namespace Slic3r::Geometry

View File

@ -31,6 +31,8 @@ public:
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<

View File

@ -23,6 +23,7 @@ using ColoredLinesIt = ColoredLines::iterator;
// Explicit template instantiation.
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, LinesIt, LinesIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, VD::SegmentIt, VD::SegmentIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, ColoredLinesIt, ColoredLinesIt);
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);