Fix of #8474 and #8514: Voronoi generator sometimes produced a non-planar Voronoi diagram.

We introduced detecting for those degeneration cases. When degenerated Voronoi diagram is detected, then the input polygons are rotated, and the Voronoi diagram is recomputed. Usually, rotation of input data solves issues like this.
This commit is contained in:
Lukáš Hejl 2022-07-20 14:10:30 +02:00
parent 9690919028
commit 63c66f4f18
3 changed files with 189 additions and 27 deletions

View file

@ -19,8 +19,14 @@
#include "Geometry/VoronoiVisualUtils.hpp"
#include "../EdgeGrid.hpp"
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Arr_segment_traits_2.h>
#include <CGAL/Surface_sweep_2_algorithms.h>
#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 ARACHNE_DEBUG
namespace boost::polygon {
template<> struct geometry_concept<Slic3r::Arachne::PolygonsSegmentIndex>
@ -285,7 +291,6 @@ 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)
{
if (cell.incident_edge()->is_infinite())
@ -386,7 +391,42 @@ SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const Bead
constructFromPolygons(polys);
}
bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
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;
inline static CGAL_Point to_cgal_point(const Voronoi::VD::vertex_type &pt)
{
return {pt.x(), pt.y()};
}
bool is_voronoi_diagram_planar(const Geometry::VoronoiDiagram &voronoi_diagram) {
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
[](const Geometry::VoronoiDiagram::edge_type &edge) { return edge.color() == 0; }));
std::vector<CGAL_Segment> segments;
segments.reserve(voronoi_diagram.num_edges());
for (const Geometry::VoronoiDiagram::edge_type &edge : voronoi_diagram.edges()) {
if (edge.color() != 0)
continue;
if (edge.is_finite() && edge.is_linear()) {
segments.emplace_back(to_cgal_point(*edge.vertex0()), to_cgal_point(*edge.vertex1()));
edge.color(1);
assert(edge.twin() != nullptr);
edge.twin()->color(1);
}
}
for (const Geometry::VoronoiDiagram::edge_type &edge : voronoi_diagram.edges())
edge.color(0);
std::vector<CGAL_Point> intersections_pt;
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
return intersections_pt.empty();
}
static bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagram, const std::vector<SkeletalTrapezoidation::Segment> &segments) {
for (VoronoiUtils::vd_t::cell_type cell : voronoi_diagram.cells()) {
if (!cell.incident_edge())
continue; // There is no spoon
@ -432,6 +472,58 @@ bool detect_missing_voronoi_vertex(const Geometry::VoronoiDiagram &voronoi_diagr
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_copy,
std::vector<SkeletalTrapezoidation::Segment> &segments,
const double fix_angle)
{
std::unordered_map<Point, Point, PointHash> vertex_mapping;
for (Polygon &poly : polys_copy)
poly.rotate(fix_angle);
assert(polys_copy.size() == polys.size());
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
assert(polys_copy[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[poly_idx][point_idx], polys_copy[poly_idx][point_idx]});
}
segments.clear();
for (size_t poly_idx = 0; poly_idx < polys_copy.size(); poly_idx++)
for (size_t point_idx = 0; point_idx < polys_copy[poly_idx].size(); point_idx++)
segments.emplace_back(&polys_copy, poly_idx, point_idx);
voronoi_diagram.clear();
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
assert(is_voronoi_diagram_planar(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)
{
// Check self intersections.
@ -450,39 +542,49 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); point_idx++)
segments.emplace_back(&polys, poly_idx, point_idx);
#ifdef ARACHNE_DEBUG
{
static int iRun = 0;
BoundingBox bbox = get_extents(polys);
SVG svg(debug_out_path("arachne_voronoi-input-%d.svg", iRun++).c_str(), bbox);
svg.draw_outline(polys, "black", scaled<coordf_t>(0.03f));
}
#endif
Geometry::VoronoiDiagram voronoi_diagram;
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
#ifdef ARACHNE_DEBUG
{
static int iRun = 0;
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
}
#endif
#ifdef ARACHNE_DEBUG
assert(is_voronoi_diagram_planar(voronoi_diagram));
#endif
// Try to detect cases when some Voronoi vertex is missing.
// When any Voronoi vertex is missing, rotate input polygon and try again.
const bool has_missing_voronoi_vertex = detect_missing_voronoi_vertex(voronoi_diagram, segments);
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) {
BOOST_LOG_TRIVIAL(debug) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
for (Polygon &poly : polys_copy)
poly.rotate(fix_angle);
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, 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(polys_copy.size() == polys.size());
for (size_t poly_idx = 0; poly_idx < polys.size(); ++poly_idx) {
assert(polys_copy[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[poly_idx][point_idx], polys_copy[poly_idx][point_idx]});
}
segments.clear();
for (size_t poly_idx = 0; poly_idx < polys_copy.size(); poly_idx++)
for (size_t point_idx = 0; point_idx < polys_copy[poly_idx].size(); point_idx++)
segments.emplace_back(&polys_copy, poly_idx, point_idx);
voronoi_diagram.clear();
construct_voronoi(segments.begin(), segments.end(), &voronoi_diagram);
assert(!detect_missing_voronoi_vertex(voronoi_diagram, segments));
if (detect_missing_voronoi_vertex(voronoi_diagram, segments))
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
}
bool degenerated_voronoi_diagram = has_missing_voronoi_vertex;
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()) {
if (!cell.incident_edge())
continue; // There is no spoon
@ -538,16 +640,41 @@ void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
prev_edge->to->data.distance_to_boundary = 0;
}
if (has_missing_voronoi_vertex) {
for (node_t &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);
}
// 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.
// FIXME Lukas H.: Replace has_missing_twin_edge with detection if the Voronoi diagram is planar using a custom algorithm based on the
// sweeping line algorithm. At least it is required to fix GH #8446.
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(is_voronoi_diagram_planar(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);
separatePointyQuadEndNodes();
graph.collapseSmallEdges();

View file

@ -18,6 +18,7 @@
#include "SkeletalTrapezoidationJoint.hpp"
#include "libslic3r/Arachne/BeadingStrategy/BeadingStrategy.hpp"
#include "SkeletalTrapezoidationGraph.hpp"
#include "../Geometry/Voronoi.hpp"
namespace Slic3r::Arachne
{
@ -591,5 +592,7 @@ protected:
void generateLocalMaximaSingleBeads();
};
bool is_voronoi_diagram_planar(const Geometry::VoronoiDiagram &voronoi_diagram);
} // namespace Slic3r::Arachne
#endif // VORONOI_QUADRILATERALIZATION_H

View file

@ -5,6 +5,7 @@
#include <libslic3r/Polyline.hpp>
#include <libslic3r/EdgeGrid.hpp>
#include <libslic3r/Geometry.hpp>
#include <libslic3r/Arachne/SkeletalTrapezoidation.hpp>
#include <libslic3r/Geometry/VoronoiOffset.hpp>
#include <libslic3r/Geometry/VoronoiVisualUtils.hpp>
@ -2158,3 +2159,34 @@ TEST_CASE("Intersecting Voronoi edges", "[Voronoi]")
// REQUIRE(!has_intersecting_edges(poly, vd));
}
// In this case resulting Voronoi diagram is not planar. This case was distilled from GH issue #8474.
// Also, in GH issue #8514, a non-planar Voronoi diagram is generated for several polygons.
// Rotating the input polygon will solve this issue.
TEST_CASE("Non-planar voronoi diagram", "[VoronoiNonPlanar]")
{
Polygon poly {
{ 5500000, -42000000},
{ 8000000, -17000000},
{ 8000000, 40000000},
{ 7500000, 40000000},
{ 7500000, -18000000},
{ 6000001, -18000000},
{ 6000000, 40000000},
{ 5500000, 40000000},
};
// poly.rotate(PI / 6);
REQUIRE(poly.area() > 0.);
REQUIRE(intersecting_edges({poly}).empty());
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-non-planar-out.svg").c_str(), vd, Points(), lines);
#endif
// REQUIRE(Arachne::is_voronoi_diagram_planar(vd));
}