mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-27 18:51:11 -06:00
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:
parent
dc4bdad84a
commit
a116914fce
6 changed files with 2018 additions and 548 deletions
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue