WIP VoronoiOffset: Squash merge of vb_voronoi_offset

Working contour offsetting,
skeleton_edges_rough() to detect "important" skeleton edges.
Radius of an inscribed circle along the "important" skeleton edges
changes slowly, therefore these "important" skeleton edges signify
oblong regions possibly needing a gap fill.
This commit is contained in:
Vojtech Bubnik 2021-01-29 16:34:22 +01:00
parent dc4bdad84a
commit a116914fce
6 changed files with 2018 additions and 548 deletions

View File

@ -417,7 +417,7 @@ namespace boost { namespace polygon {
typedef coord_t coordinate_type;
static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) {
return (coordinate_type)point((orient == HORIZONTAL) ? 0 : 1);
return static_cast<coordinate_type>(point((orient == HORIZONTAL) ? 0 : 1));
}
};

File diff suppressed because it is too large Load Diff

View File

@ -9,16 +9,136 @@
namespace Slic3r {
namespace Voronoi {
using VD = Slic3r::Geometry::VoronoiDiagram;
inline const Point& contour_point(const VD::cell_type &cell, const Line &line)
{ return ((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b); }
inline Point& contour_point(const VD::cell_type &cell, Line &line)
{ return ((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b); }
inline const Point& contour_point(const VD::cell_type &cell, const Lines &lines)
{ return contour_point(cell, lines[cell.source_index()]); }
inline Point& contour_point(const VD::cell_type &cell, Lines &lines)
{ return contour_point(cell, lines[cell.source_index()]); }
inline Vec2d vertex_point(const VD::vertex_type &v) { return Vec2d(v.x(), v.y()); }
inline Vec2d vertex_point(const VD::vertex_type *v) { return Vec2d(v->x(), v->y()); }
// "Color" stored inside the boost::polygon Voronoi vertex.
enum class VertexCategory : unsigned char
{
// Voronoi vertex is on the input contour.
// VD::vertex_type stores coordinates in double, though the coordinates shall match exactly
// with the coordinates of the input contour when converted to int32_t.
OnContour,
// Vertex is inside the CCW input contour, holes are respected.
Inside,
// Vertex is outside the CCW input contour, holes are respected.
Outside,
// Not known yet.
Unknown,
};
// "Color" stored inside the boost::polygon Voronoi edge.
// The Voronoi edge as represented by boost::polygon Voronoi module is really a half-edge,
// the half-edges are classified based on the target vertex (VD::vertex_type::vertex1())
enum class EdgeCategory : unsigned char
{
// This half-edge points onto the contour, this VD::edge_type::vertex1().color() is OnContour.
PointsToContour,
// This half-edge points inside, this VD::edge_type::vertex1().color() is Inside.
PointsInside,
// This half-edge points outside, this VD::edge_type::vertex1().color() is Outside.
PointsOutside,
// Not known yet.
Unknown
};
// "Color" stored inside the boost::polygon Voronoi cell.
enum class CellCategory : unsigned char
{
// This Voronoi cell is split by an input segment to two halves, one is inside, the other is outside.
Boundary,
// This Voronoi cell is completely inside.
Inside,
// This Voronoi cell is completely outside.
Outside,
// Not known yet.
Unknown
};
inline VertexCategory vertex_category(const VD::vertex_type &v)
{ return static_cast<VertexCategory>(v.color()); }
inline VertexCategory vertex_category(const VD::vertex_type *v)
{ return static_cast<VertexCategory>(v->color()); }
inline void set_vertex_category(VD::vertex_type &v, VertexCategory c)
{ v.color(static_cast<VD::vertex_type::color_type>(c)); }
inline void set_vertex_category(VD::vertex_type *v, VertexCategory c)
{ v->color(static_cast<VD::vertex_type::color_type>(c)); }
inline EdgeCategory edge_category(const VD::edge_type &e)
{ return static_cast<EdgeCategory>(e.color()); }
inline EdgeCategory edge_category(const VD::edge_type *e)
{ return static_cast<EdgeCategory>(e->color()); }
inline void set_edge_category(VD::edge_type &e, EdgeCategory c)
{ e.color(static_cast<VD::edge_type::color_type>(c)); }
inline void set_edge_category(VD::edge_type *e, EdgeCategory c)
{ e->color(static_cast<VD::edge_type::color_type>(c)); }
inline CellCategory cell_category(const VD::cell_type &v)
{ return static_cast<CellCategory>(v.color()); }
inline CellCategory cell_category(const VD::cell_type *v)
{ return static_cast<CellCategory>(v->color()); }
inline void set_cell_category(const VD::cell_type &v, CellCategory c)
{ v.color(static_cast<VD::cell_type::color_type>(c)); }
inline void set_cell_category(const VD::cell_type *v, CellCategory c)
{ v->color(static_cast<VD::cell_type::color_type>(c)); }
// Mark the "Color" of VD vertices, edges and cells as Unknown.
void reset_inside_outside_annotations(VD &vd);
// Assign "Color" to VD vertices, edges and cells signifying whether the entity is inside or outside
// the input polygons defined by Lines.
void annotate_inside_outside(VD &vd, const Lines &lines);
// Returns a signed distance to Voronoi vertices from the input polygons.
// (negative distances inside, positive distances outside).
std::vector<double> signed_vertex_distances(const VD &vd, const Lines &lines);
static inline bool edge_offset_no_intersection(const Vec2d &intersection_point)
{ return std::isnan(intersection_point.x()); }
static inline bool edge_offset_has_intersection(const Vec2d &intersection_point)
{ return ! edge_offset_no_intersection(intersection_point); }
std::vector<Vec2d> edge_offset_contour_intersections(
const VD &vd, const Lines &lines, const std::vector<double> &distances,
double offset_distance);
std::vector<Vec2d> skeleton_edges_rough(
const VD &vd,
const Lines &lines,
const double threshold_alpha);
Polygons offset(
const Geometry::VoronoiDiagram &vd,
const Lines &lines,
const std::vector<double> &signed_vertex_distances,
double offset_distance,
double discretization_error);
// Offset a polygon or a set of polygons possibly with holes by traversing a Voronoi diagram.
// The input polygons are stored in lines and lines are referenced by vd.
// Outer curve will be extracted for a positive offset_distance,
// inner curve will be extracted for a negative offset_distance.
// Circular arches will be discretized to achieve discretization_error.
Polygons voronoi_offset(
const Geometry::VoronoiDiagram &vd,
const Lines &lines,
double offset_distance,
double discretization_error);
Polygons offset(
const VD &vd,
const Lines &lines,
double offset_distance,
double discretization_error);
} // namespace Voronoi
} // namespace Slic3r

View File

@ -5,6 +5,8 @@
#include <libslic3r/Polygon.hpp>
#include <libslic3r/SVG.hpp>
#include "VoronoiOffset.hpp"
namespace boost { namespace polygon {
// The following code for the visualization of the boost Voronoi diagram is based on:
@ -70,6 +72,7 @@ class voronoi_visual_utils {
get_point_projection((*discretization)[0], segment);
CT projection_end = sqr_segment_length *
get_point_projection((*discretization)[1], segment);
assert(projection_start != projection_end);
// Compute parabola parameters in the transformed space.
// Parabola has next representation:
@ -99,13 +102,16 @@ class voronoi_visual_utils {
// furthest from the current line segment.
CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x;
CT mid_y = parabola_y(mid_x, rot_x, rot_y);
assert(mid_x != cur_x || mid_y != cur_y);
assert(mid_x != new_x || mid_y != new_y);
// Compute maximum distance between the given parabolic arc
// and line segment that discretize it.
CT dist = (new_y - cur_y) * (mid_x - cur_x) -
(new_x - cur_x) * (mid_y - cur_y);
dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) +
(new_x - cur_x) * (new_x - cur_x));
CT div = (new_y - cur_y) * (new_y - cur_y) + (new_x - cur_x) * (new_x - cur_x);
assert(div != 0);
dist = dist * dist / div;
if (dist <= max_dist_transformed) {
// Distance between parabola and line segment is less than max_dist.
point_stack.pop();
@ -236,50 +242,39 @@ namespace Voronoi { namespace Internal {
inline void clip_infinite_edge(const Points &points, const std::vector<segment_type> &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector<point_type>* clipped_edge)
{
assert(edge.is_infinite());
assert((edge.vertex0() == nullptr) != (edge.vertex1() == nullptr));
const cell_type& cell1 = *edge.cell();
const cell_type& cell2 = *edge.twin()->cell();
point_type origin, direction;
// Infinite edges could not be created by two segment sites.
assert(cell1.contains_point() || cell2.contains_point());
if (! cell1.contains_point() && ! cell2.contains_point()) {
printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n");
return;
}
point_type direction;
if (cell1.contains_point() && cell2.contains_point()) {
assert(! edge.is_secondary());
point_type p1 = retrieve_point(points, segments, cell1);
point_type p2 = retrieve_point(points, segments, cell2);
origin.x((p1.x() + p2.x()) * 0.5);
origin.y((p1.y() + p2.y()) * 0.5);
if (edge.vertex0() == nullptr)
std::swap(p1, p2);
direction.x(p1.y() - p2.y());
direction.y(p2.x() - p1.x());
} else {
origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1);
assert(edge.is_secondary());
segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()];
coordinate_type dx = high(segment).x() - low(segment).x();
coordinate_type dy = high(segment).y() - low(segment).y();
if ((low(segment) == origin) ^ cell1.contains_point()) {
direction.x(dy);
direction.y(-dx);
} else {
direction.x(-dy);
direction.y(dx);
}
direction.x(high(segment).y() - low(segment).y());
direction.y(low(segment).x() - high(segment).x());
}
coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y()));
if (edge.vertex0() == NULL) {
clipped_edge->push_back(point_type(
origin.x() - direction.x() * koef,
origin.y() - direction.y() * koef));
if (edge.vertex0() == nullptr) {
clipped_edge->push_back(point_type(edge.vertex1()->x() + direction.x() * koef, edge.vertex1()->y() + direction.y() * koef));
clipped_edge->push_back(point_type(edge.vertex1()->x(), edge.vertex1()->y()));
} else {
clipped_edge->push_back(
point_type(edge.vertex0()->x(), edge.vertex0()->y()));
}
if (edge.vertex1() == NULL) {
clipped_edge->push_back(point_type(
origin.x() + direction.x() * koef,
origin.y() + direction.y() * koef));
} else {
clipped_edge->push_back(
point_type(edge.vertex1()->x(), edge.vertex1()->y()));
clipped_edge->push_back(point_type(edge.vertex0()->x(), edge.vertex0()->y()));
clipped_edge->push_back(point_type(edge.vertex0()->x() + direction.x() * koef, edge.vertex0()->y() + direction.y() * koef));
}
}
@ -307,11 +302,16 @@ static inline void dump_voronoi_to_svg(
const Lines &helper_lines = Lines(),
double scale = 0)
{
const bool internalEdgesOnly = false;
BoundingBox bbox;
bbox.merge(get_extents(points));
bbox.merge(get_extents(lines));
bbox.merge(get_extents(offset_curves));
bbox.merge(get_extents(helper_lines));
for (boost::polygon::voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
bbox.merge(Point(it->x(), it->y()));
bbox.min -= (0.01 * bbox.size().cast<double>()).cast<coord_t>();
bbox.max += (0.01 * bbox.size().cast<double>()).cast<coord_t>();
@ -321,15 +321,17 @@ static inline void dump_voronoi_to_svg(
0.01
* std::min(bbox.size().x(), bbox.size().y());
else
scale /= SCALING_FACTOR;
scale *= SCALING_FACTOR;
const std::string inputSegmentPointColor = "lightseagreen";
const coord_t inputSegmentPointRadius = coord_t(0.09 * scale);
const coord_t inputSegmentPointRadius = std::max<coord_t>(1, coord_t(0.09 * scale));
const std::string inputSegmentColor = "lightseagreen";
const coord_t inputSegmentLineWidth = coord_t(0.03 * scale);
const std::string voronoiPointColor = "black";
const coord_t voronoiPointRadius = coord_t(0.06 * scale);
const std::string voronoiPointColorOutside = "red";
const std::string voronoiPointColorInside = "blue";
const coord_t voronoiPointRadius = std::max<coord_t>(1, coord_t(0.06 * scale));
const std::string voronoiLineColorPrimary = "black";
const std::string voronoiLineColorSecondary = "green";
const std::string voronoiArcColor = "red";
@ -341,7 +343,6 @@ static inline void dump_voronoi_to_svg(
const std::string helperLineColor = "orange";
const coord_t helperLineWidth = coord_t(0.04 * scale);
const bool internalEdgesOnly = false;
const bool primaryEdgesOnly = false;
::Slic3r::SVG svg(path, bbox);
@ -360,9 +361,11 @@ static inline void dump_voronoi_to_svg(
Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1)))));
// Color exterior edges.
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
if (!it->is_finite())
Voronoi::Internal::color_exterior(&(*it));
if (internalEdgesOnly) {
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it)
if (!it->is_finite())
Voronoi::Internal::color_exterior(&(*it));
}
// Draw the end points of the input polygon.
for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) {
@ -376,8 +379,19 @@ static inline void dump_voronoi_to_svg(
#if 1
// Draw voronoi vertices.
for (boost::polygon::voronoi_diagram<double>::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it)
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR)
svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius);
if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) {
const std::string *color = nullptr;
switch (Voronoi::vertex_category(*it)) {
case Voronoi::VertexCategory::OnContour: color = &voronoiPointColor; break;
case Voronoi::VertexCategory::Outside: color = &voronoiPointColorOutside; break;
case Voronoi::VertexCategory::Inside: color = &voronoiPointColorInside; break;
default: color = &voronoiPointColor; // assert(false);
}
Point pt(coord_t(it->x()), coord_t(it->y()));
if (it->x() * pt.x() >= 0. && it->y() * pt.y() >= 0.)
// Conversion to coord_t is valid.
svg.draw(Point(coord_t(it->x()), coord_t(it->y())), *color, voronoiPointRadius);
}
for (boost::polygon::voronoi_diagram<double>::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) {
if (primaryEdgesOnly && !it->is_primary())
@ -401,8 +415,32 @@ static inline void dump_voronoi_to_svg(
} else if (! it->is_primary())
color = voronoiLineColorSecondary;
}
for (std::size_t i = 0; i + 1 < samples.size(); ++i)
svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth);
for (std::size_t i = 0; i + 1 < samples.size(); ++ i) {
Vec2d a(samples[i].x(), samples[i].y());
Vec2d b(samples[i+1].x(), samples[i+1].y());
// Convert to coord_t.
Point ia = a.cast<coord_t>();
Point ib = b.cast<coord_t>();
// Is the conversion possible? Do the resulting points fit into int32_t?
auto in_range = [](const Point &ip, const Vec2d &p) { return p.x() * ip.x() >= 0. && p.y() * ip.y() >= 0.; };
bool a_in_range = in_range(ia, a);
bool b_in_range = in_range(ib, b);
if (! a_in_range || ! b_in_range) {
if (! a_in_range && ! b_in_range)
// None fits, ignore.
continue;
// One fit, the other does not. Try to clip.
Vec2d v = b - a;
v.normalize();
v *= bbox.size().cast<double>().norm();
auto p = a_in_range ? Vec2d(a + v) : Vec2d(b - v);
Point ip = p.cast<coord_t>();
if (! in_range(ip, p))
continue;
(a_in_range ? ib : ia) = ip;
}
svg.draw(Line(ia, ib), color, voronoiLineWidth);
}
}
#endif

View File

@ -228,6 +228,16 @@ ForwardIt binary_find_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey
return first != last && equal_to_key(*first) ? first : last;
}
template<typename ContainerType, typename ValueType> inline bool contains(const ContainerType &c, const ValueType &v)
{ return std::find(c.begin(), c.end(), v) != c.end(); }
template<typename T> inline bool contains(const std::initializer_list<T> &il, const T &v)
{ return std::find(il.begin(), il.end(), v) != il.end(); }
template<typename ContainerType, typename ValueType> inline bool one_of(const ValueType &v, const ContainerType &c)
{ return contains(c, v); }
template<typename T> inline bool one_of(const T& v, const std::initializer_list<T>& il)
{ return contains(il, v); }
template<typename T>
static inline T sqr(T x)
{

View File

@ -38,6 +38,26 @@ TEST_CASE("Voronoi missing edges - points 12067", "[Voronoi]")
{ -5, 0 }
};
#if 0
for (Point &p : pts) {
Vec2d q = p.cast<double>();
p.x() = scale_(p.x());
p.y() = scale_(p.y());
}
#endif
#if 0
// Try to rotate, maybe the issue is caused by incorrect handling of vertical or horizontal edges?
double a = 0.18764587962597876897475f;
double c = cos(a);
double s = sin(a);
for (Point &p : poly.points) {
Vec2d q = p.cast<double>();
p.x() = coord_t(q.x() * c + q.y() * s + 0.5);
p.y() = coord_t(- q.x() * s + q.y() * c + 0.5);
}
#endif
// Construction of the Voronoi Diagram.
VD vd;
construct_voronoi(pts.begin(), pts.end(), &vd);
@ -133,33 +153,40 @@ TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]")
{ { 41427551, 0}, { 42127548, 699996 } }
};
Lines lines = to_lines(Polygon {
{ 0, 10000000},
{ 700000, 1}, // it has to be 1, higher number, zero or -1 work.
{ 700000, 9000000},
{ 9100000, 9000000},
{ 9100000, 0},
{10000000, 10000000}
});
Polygon poly {
{ 0, 10000000},
{ 700000, 1}, // it has to be 1, higher number, zero or -1 work.
{ 700000, 9000000},
{ 9100000, 9000000},
{ 9100000, 0},
{10000000, 10000000}
};
#if 1
// Try to rotate, maybe the issue is caused by incorrect handling of vertical or horizontal edges?
double a = 0.18764587962597876897475f;
double c = cos(a);
double s = sin(a);
for (Point &p : poly.points) {
Vec2d q = p.cast<double>();
p.x() = coord_t(q.x() * c + q.y() * s + 0.5);
p.y() = coord_t(- q.x() * s + q.y() * c + 0.5);
}
#endif
Polygon poly;
std::mt19937 gen;
std::uniform_int_distribution<coord_t> dist(-100, 100);
for (size_t i = 0; i < lines.size(); ++ i) {
Line &l1 = lines[i];
Line &l2 = lines[(i + 1) % lines.size()];
REQUIRE(l1.b.x() == l2.a.x());
REQUIRE(l1.b.y() == l2.a.y());
for (Point &p : poly.points) {
#if 0
// Wiggle the points a bit to find out whether this fixes the voronoi diagram for this particular polygon.
l1.b.x() = (l2.a.x() += dist(gen));
l1.b.y() = (l2.a.y() += dist(gen));
p.x() = (p.x() += dist(gen));
p.y() = (p.y() += dist(gen));
#endif
poly.points.emplace_back(l1.a);
}
REQUIRE(intersecting_edges({ poly }).empty());
Lines lines = to_lines(poly);
VD vd;
construct_voronoi(lines.begin(), lines.end(), &vd);
@ -169,6 +196,114 @@ TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]")
#endif
}
TEST_CASE("Voronoi weirdness", "[Voronoi]")
{
Polygon poly2 {
{ 0, 0 },
{ 70000000, 0 },
{ 70000000, 1300000 },
// { 70000001, 14000000 }
{ 70700000, 14000000 }
};
Polygon poly5 {
{ 35058884, -25732145 },
{ 35058884, -19586070 },
{ 32753739, -20246796 },
{ 28756244, -21010725 },
{ 24657532, -21657939 },
{ 20836260, -21960233 },
{ 16115145, -22070742 },
{ 11850152, -21839761 },
{ 7646240, -21470177 },
{ 3607605, -20786940 },
{ 1280947, -20329742 },
{ -292823, -19963790 },
{ -3844469, -18809741 },
{ -7237277, -17593723 },
{ -10225900, -16143761 },
{ -13030266, -14643721 },
{ -15404294, -12977561 },
{ -17601713, -11280712 },
{ -19241930, -9435607 },
{ -20714420, -7583739 },
{ -21726144, -5664355 },
{ -22579294, -3741947 },
{ -22966684, -1786321 },
{ -23200322, 170140 },
{ -22966684, 2126602 },
{ -22579296, 4082227 },
{ -21726148, 6004637 },
{ -20714424, 7924020 },
{ -19241932, 9775888 },
{ -17601717, 11620994 },
{ -15404423, 13317749 },
{ -13030276, 14984003 },
{ -10225910, 16484042 },
{ -7237288, 17934005 },
{ -3844482, 19150025 },
{ -292841, 20304074 },
{ 1280949, 20670031 },
{ 3607587, 21127226 },
{ 7646218, 21810465 },
{ 11850128, 22180055 },
{ 16115122, 22411036 },
{ 20836263, 22300531 },
{ 24657513, 21998239 },
{ 28756227, 21351025 },
{ 32753725, 20587092 },
{ 35058893, 19926309 },
{ 35058893, 35000000 },
{ -31657232, 35000000 },
{ -31657202, -35000000 },
{ 35058881, -35000000 }
};
Polygon poly7 {
{ 35058884, -25732145 },
{ 35058884, -19586070 },
{ -31657202, -35000000 },
{ 35058881, -35000000 }
};
// coord_t shift = 35058881;
coord_t shift_ok = 17000000;
coord_t shift = 35058881;
Polygon poly {
// <-4, 0>: bug
// -5: ok
// 1 - ok
{ 0 + shift, -35000000 },
{ 0 + shift, -25732145 },
{ 0 + shift, -19586070 },
{ -66716086 + shift, -35000000 }
};
REQUIRE(intersecting_edges({ poly }).empty());
REQUIRE(poly.area() > 0.);
#if 0
// Try to rotate, maybe the issue is caused by incorrect handling of vertical or horizontal edges?
double a = 0.18764587962597876897475f;
double c = cos(a);
double s = sin(a);
for (Point &p : poly.points) {
Vec2d q = p.cast<double>();
p.x() = coord_t(q.x() * c + q.y() * s + 0.5);
p.y() = coord_t(- q.x() * s + q.y() * c + 0.5);
}
#endif
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-weirdness.svg").c_str(),
vd, Points(), lines);
#endif
}
// https://svn.boost.org/trac10/ticket/12903
// division by zero reported, but this issue is most likely a non-issue, as it produces an infinity for the interval of validity
// of the floating point calculation, therefore forcing a recalculation with extended accuracy.
@ -1228,7 +1363,7 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]")
for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 1, 1 },
OffsetTest { scale_(0.4), 1, 1 },
OffsetTest { scale_(0.5), 1, 1 },
OffsetTest { scale_(0.5), 1, 2 },
OffsetTest { scale_(0.505), 1, 2 },
OffsetTest { scale_(0.51), 1, 2 },
OffsetTest { scale_(0.52), 1, 1 },
@ -1237,21 +1372,21 @@ TEST_CASE("Voronoi offset", "[VoronoiOffset]")
OffsetTest { scale_(0.55), 1, 0 }
}) {
Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005));
REQUIRE(offsetted_polygons_out.size() == ot.num_outer);
#if 0
Polygons offsetted_polygons_out = Slic3r::Voronoi::offset(vd, lines, ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset-out-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_out);
#endif
REQUIRE(offsetted_polygons_out.size() == ot.num_outer);
#endif
Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005));
REQUIRE(offsetted_polygons_in.size() == ot.num_inner);
Polygons offsetted_polygons_in = Slic3r::Voronoi::offset(vd, lines, - ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset-in-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_in);
#endif
REQUIRE(offsetted_polygons_in.size() == ot.num_inner);
}
}
@ -1296,21 +1431,20 @@ TEST_CASE("Voronoi offset 2", "[VoronoiOffset]")
OffsetTest { scale_(0.4), 2, 2 },
OffsetTest { scale_(0.45), 2, 2 },
OffsetTest { scale_(0.48), 2, 2 },
//FIXME Exact intersections of an Offset curve with any Voronoi vertex are not handled correctly yet.
// OffsetTest { scale_(0.5), 2, 2 },
OffsetTest { scale_(0.5), 2, 4 },
OffsetTest { scale_(0.505), 2, 4 },
OffsetTest { scale_(0.7), 2, 0 },
OffsetTest { scale_(0.8), 1, 0 }
}) {
Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005));
Polygons offsetted_polygons_out = Slic3r::Voronoi::offset(vd, lines, ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_out);
#endif
REQUIRE(offsetted_polygons_out.size() == ot.num_outer);
Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005));
Polygons offsetted_polygons_in = Slic3r::Voronoi::offset(vd, lines, - ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_in);
@ -1360,14 +1494,13 @@ TEST_CASE("Voronoi offset 3", "[VoronoiOffset]")
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
construct_voronoi(lines.begin(), lines.end(), &vd);
for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 },
OffsetTest { scale_(0.4), 2, 2 },
OffsetTest { scale_(0.49), 2, 2 },
//FIXME this fails
// OffsetTest { scale_(0.5), 2, 2 },
OffsetTest { scale_(0.5), 2, 2 },
OffsetTest { scale_(0.51), 2, 2 },
OffsetTest { scale_(0.56), 2, 2 },
OffsetTest { scale_(0.6), 2, 2 },
@ -1375,23 +1508,417 @@ TEST_CASE("Voronoi offset 3", "[VoronoiOffset]")
OffsetTest { scale_(0.8), 1, 6 },
OffsetTest { scale_(0.9), 1, 6 },
OffsetTest { scale_(0.99), 1, 6 },
//FIXME this fails
// OffsetTest { scale_(1.0), 1, 6 },
OffsetTest { scale_(1.0), 1, 0 },
OffsetTest { scale_(1.01), 1, 0 },
}) {
Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005));
Polygons offsetted_polygons_out = Slic3r::Voronoi::offset(vd, lines, ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(),
dump_voronoi_to_svg(debug_out_path("voronoi-offset3-out-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_out);
#endif
REQUIRE(offsetted_polygons_out.size() == ot.num_outer);
Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005));
Polygons offsetted_polygons_in = Slic3r::Voronoi::offset(vd, lines, - ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(),
dump_voronoi_to_svg(debug_out_path("voronoi-offset3-in-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_in);
#endif
REQUIRE(offsetted_polygons_in.size() == ot.num_inner);
}
}
TEST_CASE("Voronoi offset with edge collapse", "[VoronoiOffset4]")
{
Polygons poly
{
// Outer contour
{
{ 434890, -64754575 },
{ 541060, -64669076 },
{ 585647, -64583538 },
{ 576999, -64484233 },
{ 485666, -64191173 },
{ 1029323, -63646261 },
{ 1416925, -63666243 },
{ 1541532, -63643075 },
{ 1541535, -63643075 },
{ 1541535, -63643074 },
{ 1552612, -63641015 },
{ 1631609, -63568270 },
{ 1678393, -63443639 },
{ 1725519, -63219942 },
{ 1770552, -62903321 },
{ 1742544, -62755934 },
{ 1758152, -62588378 },
{ 1702289, -62474826 },
{ 1638627, -62242058 },
{ 1671281, -61938568 },
{ 1792676, -61478332 },
{ 1934894, -60760597 },
{ 2160333, -60064089 },
{ 2638183, -58972890 },
{ 2933095, -58478536 },
{ 3173586, -58159527 },
{ 3557779, -57556727 },
{ 3707088, -57158174 },
{ 3758053, -56907139 },
{ 3935932, -56278649 },
{ 4077331, -56033426 },
{ 4151386, -55768739 },
{ 4293796, -55561683 },
{ 4383089, -55387391 },
{ 4614128, -55201443 },
{ 5268755, -54333831 },
{ 5547634, -53797296 },
{ 5573864, -53639250 },
{ 5736812, -53304157 },
{ 6090973, -52066302 },
{ 6148630, -51666664 },
{ 6189511, -50974991 },
{ 6244112, -50774489 },
{ 6302489, -50761556 },
{ 6511179, -50579861 },
{ 6778128, -50398667 },
{ 6896879, -50350264 },
{ 7388259, -49913262 },
{ 7630584, -49602449 },
{ 7886846, -48987881 },
{ 7871333, -48318666 },
{ 7770349, -47841179 },
{ 7570223, -47234733 },
{ 7283115, -46705503 },
{ 6842948, -46056539 },
{ 6612732, -45760004 },
{ 6150430, -44991494 },
{ 5564326, -44168114 },
{ 5193032, -43807544 },
{ 4932097, -43489047 },
{ 3857385, -42548846 },
{ 3764745, -42081468 },
{ 3539887, -41422273 },
{ 3283048, -40803856 },
{ 3005925, -40367981 },
{ 3136185, -39834533 },
{ 3333757, -38499972 },
{ 3360660, -38183895 },
{ 3353375, -38036005 },
{ 3398808, -37582504 },
{ 3604229, -37221781 },
{ 3698079, -36962934 },
{ 4000449, -36007804 },
{ 4256811, -35016707 },
{ 4405951, -34581428 },
{ 4364534, -34178439 },
{ 4124603, -33432250 },
{ 3806159, -32765167 },
{ 3359088, -32109020 },
{ 2880790, -31317192 },
{ 1548334, -30402139 },
{ -7, -30131023 },
{ -1548347, -30402139 },
{ -2880803, -31317189 },
{ -3359100, -32109018 },
{ -3806173, -32765166 },
{ -4124617, -33432248 },
{ -4364548, -34178436 },
{ -4405966, -34581423 },
{ -4256825, -35016707 },
{ -4000461, -36007800 },
{ -3698093, -36962933 },
{ -3604243, -37221781 },
{ -3398823, -37582501 },
{ -3353390, -38036003 },
{ -3360675, -38183892 },
{ -3333772, -38499972 },
{ -3136200, -39834530 },
{ -3005940, -40367980 },
{ -3283062, -40803852 },
{ -3539902, -41422273 },
{ -3764761, -42081468 },
{ -3857402, -42548846 },
{ -4932112, -43489047 },
{ -5193047, -43807544 },
{ -5564341, -44168114 },
{ -6150446, -44991494 },
{ -6612748, -45760000 },
{ -6842965, -46056536 },
{ -7283130, -46705503 },
{ -7570238, -47234733 },
{ -7770365, -47841179 },
{ -7871349, -48318666 },
{ -7886860, -48987873 },
{ -7630599, -49602451 },
{ -7388277, -49913260 },
{ -6896896, -50350264 },
{ -6778145, -50398667 },
{ -6511196, -50579862 },
{ -6302504, -50761557 },
{ -6244127, -50774488 },
{ -6189527, -50974989 },
{ -6148648, -51666664 },
{ -6090990, -52066302 },
{ -5736829, -53304157 },
{ -5573882, -53639250 },
{ -5547651, -53797296 },
{ -5268772, -54333831 },
{ -4614145, -55201443 },
{ -4383106, -55387389 },
{ -4293814, -55561683 },
{ -4151404, -55768739 },
{ -4077350, -56033423 },
{ -3935952, -56278647 },
{ -3758072, -56907137 },
{ -3707107, -57158170 },
{ -3557796, -57556727 },
{ -3173605, -58159527 },
{ -2933113, -58478536 },
{ -2638201, -58972890 },
{ -2295003, -59738435 },
{ -2160353, -60064089 },
{ -1978487, -60596626 },
{ -1792695, -61478332 },
{ -1671298, -61938574 },
{ -1638646, -62242058 },
{ -1702306, -62474826 },
{ -1758168, -62588380 },
{ -1742563, -62755934 },
{ -1770570, -62903317 },
{ -1725537, -63219946 },
{ -1678412, -63443639 },
{ -1631627, -63568270 },
{ -1553479, -63640201 },
{ -1416944, -63666243 },
{ -998854, -63645714 },
{ -485685, -64191173 },
{ -577019, -64484225 },
{ -585667, -64583538 },
{ -541079, -64669076 },
{ -434910, -64754575 },
{ -294192, -64810609 },
{ 294172, -64810609 },
},
// Hole 1
{
{ -842246, -45167428 },
{ -1473641, -45154392 },
{ -2181728, -45100161 },
{ -2804308, -44985842 },
{ -3100514, -44879269 },
{ -3836807, -44802155 },
{ -4035816, -44718913 },
{ -4167175, -44583299 },
{ -4496705, -44467674 },
{ -4486674, -44382045 },
{ -4343949, -44070577 },
{ -3740991, -43494686 },
{ -2701372, -43313358 },
{ -1493599, -43312838 },
{ -8, -43352868 },
{ 1414575, -43313336 },
{ 2701358, -43313358 },
{ 3095462, -43374735 },
{ 3740975, -43494686 },
{ 4343934, -44070583 },
{ 4486657, -44382044 },
{ 4496688, -44467670 },
{ 4167159, -44583300 },
{ 4035800, -44718913 },
{ 3836792, -44802155 },
{ 3100498, -44879269 },
{ 2804292, -44985842 },
{ 2181712, -45100161 },
{ 1473625, -45154389 },
{ 842231, -45167428 },
{ -8, -45232686 },
},
// Hole 2
{
{ 1657455, -63016679 },
{ 1723196, -63056286 },
{ 1541535, -63643074 },
{ 1541532, -63643075 },
{ 1030020, -63645562 },
}
};
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
for (const OffsetTest &ot : {
OffsetTest { scale_(0.2), 2, 2 },
OffsetTest { scale_(0.4), 2, 2 },
OffsetTest { scale_(0.49), 2, 3 },
OffsetTest { scale_(0.51), 2, 2 },
OffsetTest { scale_(0.56), 2, 2 },
OffsetTest { scale_(0.6), 2, 2 },
OffsetTest { scale_(0.7), 2, 2 },
OffsetTest { scale_(0.8), 2, 2 },
OffsetTest { scale_(0.9), 2, 2 },
OffsetTest { scale_(0.99), 1, 2 },
OffsetTest { scale_(1.0), 1, 2 },
OffsetTest { scale_(1.01), 1, 2 },
}) {
Polygons offsetted_polygons_out = Slic3r::Voronoi::offset(vd, lines, ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset3-out-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_out);
#endif
REQUIRE(offsetted_polygons_out.size() == ot.num_outer);
Polygons offsetted_polygons_in = Slic3r::Voronoi::offset(vd, lines, - ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset3-in-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_in);
#endif
REQUIRE(offsetted_polygons_in.size() == ot.num_inner);
}
}
// A sample extracted from file medallion_printable_fixed-teeth.stl from https://www.thingiverse.com/thing:1347129
// This test for offset scale_(2.9) and bigger
// triggers assert(r < std::max(d0, d1) + EPSILON) in function first_circle_segment_intersection_parameter.
TEST_CASE("Voronoi offset 5", "[VoronoiOffset5]")
{
Polygons poly = {
Polygon {
{ 0 , -16077501 },
{ 3015222 , -16142836 },
{ 3072642 , -16138163 },
{ 3094279 , -16105662 },
{ 3110660 , -15140728 },
{ 3157438 , -14105326 },
{ 3338367 , -11081394 },
{ 3443412 , -8381621 },
{ 3472489 , -6084497 },
{ 3445790 , -5962924 },
{ 3061725 , -6003484 },
{ 3030326 , -6030622 },
{ 2989343 , -6270378 },
{ 2903752 , -7368176 },
{ 2856704 , -7740619 },
{ 2795743 , -7978809 },
{ 2729231 , -8098866 },
{ 2666509 , -8131138 },
{ 2614169 , -8112308 },
{ 2561157 , -8032014 },
{ 2488290 , -7479351 },
{ 2453360 , -6911556 },
{ 2456148 , -6463146 },
{ 2546029 , -4580396 },
{ 2688181 , -2593262 },
{ 2717617 , -1700519 },
{ 2682232 , -1128411 },
{ 2631029 , -832886 },
{ 2535941 , -504483 },
{ 2399115 , -199303 },
{ 2290997 , -171213 },
{ 611824 , -132865 },
{ 6419 , -375849 },
{ -611825 , -132865 },
{ -2377355 , -185241 },
{ -2420740 , -231171 },
{ -2535942 , -504484 },
{ -2631030 , -832887 },
{ -2684831 , -1150821 },
{ -2714840 , -1586454 },
{ -2688181 , -2593262 },
{ -2546030 , -4580396 },
{ -2456149 , -6463145 },
{ -2453361 , -6911557 },
{ -2488291 , -7479352 },
{ -2561159 , -8032018 },
{ -2614171 , -8112309 },
{ -2666509 , -8131138 },
{ -2729233 , -8098868 },
{ -2795744 , -7978809 },
{ -2856706 , -7740619 },
{ -2903752 , -7368176 },
{ -2989345 , -6270378 },
{ -3030327 , -6030622 },
{ -3061726 , -6003484 },
{ -3445790 , -5962924 },
{ -3472490 , -6084498 },
{ -3468804 , -7244095 },
{ -3399287 , -9714025 },
{ -3338368 , -11081395 },
{ -3141015 , -14446051 },
{ -3094280 , -16105662 },
{ -3072643 , -16138163 },
{ -3008836 , -16143225 }
}
};
double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly){ return a + poly.area(); });
REQUIRE(area > 0.);
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
for (const OffsetTest &ot : {
OffsetTest { scale_(2.8), 1, 1 },
OffsetTest { scale_(2.9), 1, 1 },
OffsetTest { scale_(3.0), 1, 1 },
}) {
Polygons offsetted_polygons_out = Slic3r::Voronoi::offset(vd, lines, ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset5-out-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_out);
#endif
REQUIRE(offsetted_polygons_out.size() == ot.num_outer);
Polygons offsetted_polygons_in = Slic3r::Voronoi::offset(vd, lines, - ot.distance, scale_(0.005));
#ifdef VORONOI_DEBUG_OUT
dump_voronoi_to_svg(debug_out_path("voronoi-offset5-in-%lf.svg", ot.distance).c_str(),
vd, Points(), lines, offsetted_polygons_in);
#endif
REQUIRE(offsetted_polygons_in.size() == ot.num_inner);
}
}
TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]")
{
coord_t mm = coord_t(scale_(1.));
Polygons poly = {
Polygon {
{ 0, 0 },
{ 1, 0 },
{ 1, 1 },
{ 2, 1 },
{ 2, 0 },
{ 3, 0 },
{ 3, 2 },
{ 0, 2 }
},
Polygon {
{ 0, - 1 - 2 },
{ 3, - 1 - 2 },
{ 3, - 1 - 0 },
{ 2, - 1 - 0 },
{ 2, - 1 - 1 },
{ 1, - 1 - 1 },
{ 1, - 1 - 0 },
{ 0, - 1 - 0 }
},
};
for (Polygon &p : poly)
for (Point &pt : p.points)
pt *= mm;
double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly){ return a + poly.area(); });
REQUIRE(area > 0.);
VD vd;
Lines lines = to_lines(poly);
construct_voronoi(lines.begin(), lines.end(), &vd);
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
Slic3r::Voronoi::annotate_inside_outside(vd, lines);
static constexpr double threshold_alpha = M_PI / 12.; // 30 degrees
std::vector<Vec2d> skeleton_edges = Slic3r::Voronoi::skeleton_edges_rough(vd, lines, threshold_alpha);
REQUIRE(! skeleton_edges.empty());
}