mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-01 16:38:43 +03:00
Prusa 2.7.2
This commit is contained in:
@@ -120,6 +120,7 @@ static inline bool circle_approximation_sufficient(const Circle &circle, const P
|
||||
return true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static inline bool get_deviation_sum_squared(const Circle &circle, const Points::const_iterator begin, const Points::const_iterator end, const double tolerance, double &total_deviation)
|
||||
{
|
||||
// The circle was calculated from the 1st and last point of the point sequence, thus the fitting of those points does not need to be evaluated.
|
||||
@@ -148,6 +149,7 @@ static inline bool get_deviation_sum_squared(const Circle &circle, const Points:
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
double arc_fit_variance(const Point &start_pos, const Point &end_pos, const float radius, bool is_ccw,
|
||||
const Points::const_iterator begin, const Points::const_iterator end)
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include "clipper.hpp"
|
||||
#include "VoronoiOffset.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifdef SLIC3R_DEBUG
|
||||
namespace boost { namespace polygon {
|
||||
@@ -463,7 +466,19 @@ void MedialAxis::build(ThickPolylines* polylines)
|
||||
test(l.b.y());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
construct_voronoi(m_lines.begin(), m_lines.end(), &m_vd);
|
||||
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
|
||||
|
||||
// For several ExPolygons in SPE-1729, an invalid Voronoi diagram was produced that wasn't fixable by rotating input data.
|
||||
// Those ExPolygons contain very thin lines and holes formed by very close (1-5nm) vertices that are on the edge of our resolution.
|
||||
// Those thin lines and holes are both unprintable and cause the Voronoi diagram to be invalid.
|
||||
// So we filter out such thin lines and holes and try to compute the Voronoi diagram again.
|
||||
if (!m_vd.is_valid()) {
|
||||
m_lines = to_lines(closing_ex({m_expolygon}, float(2. * SCALED_EPSILON)));
|
||||
m_vd.construct_voronoi(m_lines.begin(), m_lines.end());
|
||||
|
||||
if (!m_vd.is_valid())
|
||||
BOOST_LOG_TRIVIAL(error) << "MedialAxis - Invalid Voronoi diagram even after morphological closing.";
|
||||
}
|
||||
Slic3r::Voronoi::annotate_inside_outside(m_vd, m_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);
|
||||
|
||||
354
src/libslic3r/Geometry/Voronoi.cpp
Normal file
354
src/libslic3r/Geometry/Voronoi.cpp
Normal file
@@ -0,0 +1,354 @@
|
||||
#include "Voronoi.hpp"
|
||||
|
||||
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
#include "libslic3r/Geometry/VoronoiUtils.hpp"
|
||||
#include "libslic3r/Geometry/VoronoiUtilsCgal.hpp"
|
||||
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_iterator;
|
||||
|
||||
// Explicit template instantiation.
|
||||
template void VoronoiDiagram::construct_voronoi(LinesIt, LinesIt, bool);
|
||||
template void VoronoiDiagram::construct_voronoi(ColoredLinesConstIt, ColoredLinesConstIt, bool);
|
||||
template void VoronoiDiagram::construct_voronoi(PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt, bool);
|
||||
|
||||
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,
|
||||
void>::type
|
||||
VoronoiDiagram::construct_voronoi(const SegmentIterator segment_begin, const SegmentIterator segment_end, const bool try_to_repair_if_needed) {
|
||||
boost::polygon::construct_voronoi(segment_begin, segment_end, &m_voronoi_diagram);
|
||||
if (try_to_repair_if_needed) {
|
||||
if (m_issue_type = detect_known_issues(*this, segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
|
||||
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected missing Voronoi vertex, input polygons will be rotated back and forth.";
|
||||
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected non-planar Voronoi diagram, input polygons will be rotated back and forth.";
|
||||
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected Voronoi edge intersecting input segment, input polygons will be rotated back and forth.";
|
||||
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Detected finite Voronoi vertex with non finite vertex, input polygons will be rotated back and forth.";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue, input polygons will be rotated back and forth.";
|
||||
}
|
||||
|
||||
if (m_issue_type = try_to_repair_degenerated_voronoi_diagram(segment_begin, segment_end); m_issue_type != IssueType::NO_ISSUE_DETECTED) {
|
||||
if (m_issue_type == IssueType::MISSING_VORONOI_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected missing Voronoi vertex even after the rotation of input.";
|
||||
} else if (m_issue_type == IssueType::NON_PLANAR_VORONOI_DIAGRAM) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected non-planar Voronoi diagram even after the rotation of input.";
|
||||
} else if (m_issue_type == IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected Voronoi edge intersecting input segment even after the rotation of input.";
|
||||
} else if (m_issue_type == IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected finite Voronoi vertex with non finite vertex even after the rotation of input.";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Detected unknown Voronoi diagram issue even after the rotation of input.";
|
||||
}
|
||||
|
||||
m_state = State::REPAIR_UNSUCCESSFUL;
|
||||
} else {
|
||||
m_state = State::REPAIR_SUCCESSFUL;
|
||||
}
|
||||
} else {
|
||||
m_state = State::REPAIR_NOT_NEEDED;
|
||||
m_issue_type = IssueType::NO_ISSUE_DETECTED;
|
||||
}
|
||||
} else {
|
||||
m_state = State::UNKNOWN;
|
||||
m_issue_type = IssueType::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void VoronoiDiagram::clear()
|
||||
{
|
||||
if (m_is_modified) {
|
||||
m_vertices.clear();
|
||||
m_edges.clear();
|
||||
m_cells.clear();
|
||||
m_is_modified = false;
|
||||
} else {
|
||||
m_voronoi_diagram.clear();
|
||||
}
|
||||
|
||||
m_state = State::UNKNOWN;
|
||||
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<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<SegmentIterator>::value_type>::type>::type>::type,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end)
|
||||
{
|
||||
if (has_finite_edge_with_non_finite_vertex(voronoi_diagram)) {
|
||||
return IssueType::FINITE_EDGE_WITH_NON_FINITE_VERTEX;
|
||||
} else if (const IssueType cell_issue_type = detect_known_voronoi_cell_issues(voronoi_diagram, segment_begin, segment_end); cell_issue_type != IssueType::NO_ISSUE_DETECTED) {
|
||||
return cell_issue_type;
|
||||
} else if (!VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(voronoi_diagram, segment_begin, segment_end)) {
|
||||
// Detection of non-planar Voronoi diagram detects at least GH issues #8474, #8514 and #8446.
|
||||
return IssueType::NON_PLANAR_VORONOI_DIAGRAM;
|
||||
}
|
||||
|
||||
return IssueType::NO_ISSUE_DETECTED;
|
||||
}
|
||||
|
||||
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,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram,
|
||||
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>;
|
||||
|
||||
for (VD::cell_type cell : voronoi_diagram.cells()) {
|
||||
if (cell.is_degenerate() || !cell.contains_segment())
|
||||
continue; // Skip degenerated cell that has no spoon. Also, skip a cell that doesn't contain a segment.
|
||||
|
||||
if (const SegmentCellRange cell_range = VoronoiUtils::compute_segment_cell_range(cell, segment_begin, segment_end); cell_range.is_valid()) {
|
||||
// Detection if Voronoi edge is intersecting input segment.
|
||||
// It detects this type of issue at least in GH issues #8446, #8474 and #8514.
|
||||
|
||||
const Segment &source_segment = Geometry::VoronoiUtils::get_source_segment(cell, segment_begin, segment_end);
|
||||
const Vec2d source_segment_from = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::LOW).template cast<double>();
|
||||
const Vec2d source_segment_to = boost::polygon::segment_traits<Segment>::get(source_segment, boost::polygon::HIGH).template cast<double>();
|
||||
const Vec2d source_segment_vec = source_segment_to - source_segment_from;
|
||||
|
||||
// All Voronoi vertices must be on the left side of the source segment, otherwise the Voronoi diagram is invalid.
|
||||
for (const VD::edge_type *edge = cell_range.edge_begin; edge != cell_range.edge_end; edge = edge->next()) {
|
||||
if (edge->is_infinite()) {
|
||||
// When there is a missing Voronoi vertex, we may encounter an infinite Voronoi edge.
|
||||
// This happens, for example, in GH issue #8846.
|
||||
return IssueType::MISSING_VORONOI_VERTEX;
|
||||
} else if (const Vec2d edge_v1(edge->vertex1()->x(), edge->vertex1()->y()); Slic3r::cross2(source_segment_vec, edge_v1 - source_segment_from) < 0) {
|
||||
return IssueType::VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// When there is a missing Voronoi vertex (especially at one of the endpoints of the input segment),
|
||||
// the returned cell_range is marked as invalid.
|
||||
// It detects this type of issue at least in GH issue #8846.
|
||||
return IssueType::MISSING_VORONOI_VERTEX;
|
||||
}
|
||||
}
|
||||
|
||||
return IssueType::NO_ISSUE_DETECTED;
|
||||
}
|
||||
|
||||
bool VoronoiDiagram::has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram)
|
||||
{
|
||||
for (const voronoi_diagram_type::edge_type &edge : voronoi_diagram.edges()) {
|
||||
if (edge.is_finite()) {
|
||||
assert(edge.vertex0() != nullptr && edge.vertex1() != nullptr);
|
||||
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr || !VoronoiUtils::is_finite(*edge.vertex0()) || !VoronoiUtils::is_finite(*edge.vertex1()))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram(const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
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>
|
||||
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,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
VoronoiDiagram::try_to_repair_degenerated_voronoi_diagram_by_rotation(const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end,
|
||||
const double fix_angle)
|
||||
{
|
||||
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
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "../Line.hpp"
|
||||
#include "../Polyline.hpp"
|
||||
|
||||
#define BOOST_VORONOI_USE_GMP 1
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// Suppress warning C4146 in OpenVDB: unary minus operator applied to unsigned type, result still unsigned
|
||||
@@ -16,18 +15,181 @@
|
||||
#pragma warning(pop)
|
||||
#endif // _MSC_VER
|
||||
|
||||
namespace Slic3r {
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
namespace Geometry {
|
||||
|
||||
class VoronoiDiagram : public boost::polygon::voronoi_diagram<double> {
|
||||
class VoronoiDiagram
|
||||
{
|
||||
public:
|
||||
typedef double coord_type;
|
||||
typedef boost::polygon::point_data<coordinate_type> point_type;
|
||||
typedef boost::polygon::segment_data<coordinate_type> segment_type;
|
||||
typedef boost::polygon::rectangle_data<coordinate_type> rect_type;
|
||||
using coord_type = double;
|
||||
using voronoi_diagram_type = boost::polygon::voronoi_diagram<coord_type>;
|
||||
using point_type = boost::polygon::point_data<voronoi_diagram_type::coordinate_type>;
|
||||
using segment_type = boost::polygon::segment_data<voronoi_diagram_type::coordinate_type>;
|
||||
using rect_type = boost::polygon::rectangle_data<voronoi_diagram_type::coordinate_type>;
|
||||
|
||||
using coordinate_type = voronoi_diagram_type::coordinate_type;
|
||||
using vertex_type = voronoi_diagram_type::vertex_type;
|
||||
using edge_type = voronoi_diagram_type::edge_type;
|
||||
using cell_type = voronoi_diagram_type::cell_type;
|
||||
|
||||
using const_vertex_iterator = voronoi_diagram_type::const_vertex_iterator;
|
||||
using const_edge_iterator = voronoi_diagram_type::const_edge_iterator;
|
||||
using const_cell_iterator = voronoi_diagram_type::const_cell_iterator;
|
||||
|
||||
using vertex_container_type = voronoi_diagram_type::vertex_container_type;
|
||||
using edge_container_type = voronoi_diagram_type::edge_container_type;
|
||||
using cell_container_type = voronoi_diagram_type::cell_container_type;
|
||||
|
||||
enum class IssueType {
|
||||
NO_ISSUE_DETECTED,
|
||||
FINITE_EDGE_WITH_NON_FINITE_VERTEX,
|
||||
MISSING_VORONOI_VERTEX,
|
||||
NON_PLANAR_VORONOI_DIAGRAM,
|
||||
VORONOI_EDGE_INTERSECTING_INPUT_SEGMENT,
|
||||
UNKNOWN // Repairs are disabled in the constructor.
|
||||
};
|
||||
|
||||
enum class State {
|
||||
REPAIR_NOT_NEEDED, // The original Voronoi diagram doesn't have any issue.
|
||||
REPAIR_SUCCESSFUL, // The original Voronoi diagram has some issues, but it was repaired.
|
||||
REPAIR_UNSUCCESSFUL, // The original Voronoi diagram has some issues, but it wasn't repaired.
|
||||
UNKNOWN // Repairs are disabled in the constructor.
|
||||
};
|
||||
|
||||
VoronoiDiagram() = default;
|
||||
|
||||
virtual ~VoronoiDiagram() = default;
|
||||
|
||||
IssueType get_issue_type() const { return m_issue_type; }
|
||||
|
||||
State get_state() const { return m_state; }
|
||||
|
||||
bool is_valid() const { return m_state != State::REPAIR_UNSUCCESSFUL; }
|
||||
|
||||
void clear();
|
||||
|
||||
const vertex_container_type &vertices() const { return m_is_modified ? m_vertices : m_voronoi_diagram.vertices(); }
|
||||
|
||||
const edge_container_type &edges() const { return m_is_modified ? m_edges : m_voronoi_diagram.edges(); }
|
||||
|
||||
const cell_container_type &cells() const { return m_is_modified ? m_cells : m_voronoi_diagram.cells(); }
|
||||
|
||||
std::size_t num_vertices() const { return m_is_modified ? m_vertices.size() : m_voronoi_diagram.num_vertices(); }
|
||||
|
||||
std::size_t num_edges() const { return m_is_modified ? m_edges.size() : m_voronoi_diagram.num_edges(); }
|
||||
|
||||
std::size_t num_cells() const { return m_is_modified ? m_cells.size() : m_voronoi_diagram.num_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,
|
||||
void>::type
|
||||
construct_voronoi(SegmentIterator segment_begin, SegmentIterator segment_end, bool try_to_repair_if_needed = true);
|
||||
|
||||
template<typename PointIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
|
||||
void>::type
|
||||
construct_voronoi(const PointIterator first, const PointIterator last)
|
||||
{
|
||||
boost::polygon::construct_voronoi(first, last, &m_voronoi_diagram);
|
||||
m_state = State::UNKNOWN;
|
||||
m_issue_type = IssueType::UNKNOWN;
|
||||
}
|
||||
|
||||
template<typename PointIterator, typename SegmentIterator>
|
||||
typename boost::polygon::enable_if<
|
||||
typename boost::polygon::gtl_and<
|
||||
typename boost::polygon::gtl_if<typename boost::polygon::is_point_concept<
|
||||
typename boost::polygon::geometry_concept<typename std::iterator_traits<PointIterator>::value_type>::type>::type>::type,
|
||||
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>::type,
|
||||
void>::type
|
||||
construct_voronoi(const PointIterator p_first, const PointIterator p_last, const SegmentIterator s_first, const SegmentIterator s_last)
|
||||
{
|
||||
boost::polygon::construct_voronoi(p_first, p_last, s_first, s_last, &m_voronoi_diagram);
|
||||
m_state = State::UNKNOWN;
|
||||
m_issue_type = IssueType::UNKNOWN;
|
||||
}
|
||||
|
||||
// Try to detect cases when some Voronoi vertex is missing, when the Voronoi diagram
|
||||
// is not planar or some Voronoi edge is intersecting input segment.
|
||||
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,
|
||||
IssueType>::type
|
||||
detect_known_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
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,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
try_to_repair_degenerated_voronoi_diagram_by_rotation(SegmentIterator segment_begin, SegmentIterator segment_end, double fix_angle);
|
||||
|
||||
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,
|
||||
VoronoiDiagram::IssueType>::type
|
||||
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
|
||||
// missing at one of the endpoints of the input segment.
|
||||
// The second type of issue that can be detected is a Voronoi edge that intersects the input segment.
|
||||
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,
|
||||
IssueType>::type
|
||||
detect_known_voronoi_cell_issues(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
static bool has_finite_edge_with_non_finite_vertex(const VoronoiDiagram &voronoi_diagram);
|
||||
|
||||
voronoi_diagram_type m_voronoi_diagram;
|
||||
vertex_container_type m_vertices;
|
||||
edge_container_type m_edges;
|
||||
cell_container_type m_cells;
|
||||
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 Slicer::Geometry
|
||||
} // 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_
|
||||
|
||||
283
src/libslic3r/Geometry/VoronoiUtils.cpp
Normal file
283
src/libslic3r/Geometry/VoronoiUtils.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <Arachne/utils/PolygonsSegmentIndex.hpp>
|
||||
#include <MultiMaterialSegmentation.hpp>
|
||||
|
||||
#include "VoronoiUtils.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesIt = ColoredLines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_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 ColoredLinesConstIt::reference VoronoiUtils::get_source_segment(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
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 &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template Point VoronoiUtils::get_source_point(const VoronoiDiagram::cell_type &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, LinesIt, LinesIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, VD::SegmentIt, VD::SegmentIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const VoronoiDiagram::cell_type &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template SegmentCellRange<Point> VoronoiUtils::compute_segment_cell_range(const 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(const 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;
|
||||
const 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)
|
||||
{
|
||||
assert(vertex != nullptr);
|
||||
return VoronoiUtils::to_point(*vertex);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
120
src/libslic3r/Geometry/VoronoiUtils.hpp
Normal file
120
src/libslic3r/Geometry/VoronoiUtils.hpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#ifndef slic3r_VoronoiUtils_hpp_
|
||||
#define slic3r_VoronoiUtils_hpp_
|
||||
|
||||
#include "libslic3r/Geometry/Voronoi.hpp"
|
||||
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
|
||||
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.
|
||||
const VD::edge_type *edge_begin = nullptr; // The edge of the Voronoi diagram where the loop around the cell starts.
|
||||
const 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
|
||||
{
|
||||
public:
|
||||
static Vec2i64 to_point(const VD::vertex_type *vertex);
|
||||
|
||||
static Vec2i64 to_point(const VD::vertex_type &vertex);
|
||||
|
||||
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<
|
||||
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(const VD::cell_type &cell, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
template<typename T> static bool is_in_range(const VD::vertex_type &vertex)
|
||||
{
|
||||
return VoronoiUtils::is_finite(vertex) && is_in_range<T>(vertex.x()) && is_in_range<T>(vertex.y());
|
||||
}
|
||||
|
||||
template<typename T> static bool is_in_range(const VD::edge_type &edge)
|
||||
{
|
||||
if (edge.vertex0() == nullptr || edge.vertex1() == nullptr)
|
||||
return false;
|
||||
|
||||
return is_in_range<T>(*edge.vertex0()) && is_in_range<T>(*edge.vertex1());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Slic3r::Geometry
|
||||
|
||||
#endif // slic3r_VoronoiUtils_hpp_
|
||||
@@ -3,15 +3,25 @@
|
||||
#include <CGAL/Surface_sweep_2_algorithms.h>
|
||||
|
||||
#include "libslic3r/Geometry/Voronoi.hpp"
|
||||
#include "libslic3r/Arachne/utils/VoronoiUtils.hpp"
|
||||
#include "libslic3r/Geometry/VoronoiUtils.hpp"
|
||||
#include "libslic3r/Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
#include "libslic3r/MultiMaterialSegmentation.hpp"
|
||||
|
||||
#include "VoronoiUtilsCgal.hpp"
|
||||
|
||||
using VD = Slic3r::Geometry::VoronoiDiagram;
|
||||
using namespace Slic3r::Arachne;
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
|
||||
using PolygonsSegmentIndexConstIt = std::vector<Arachne::PolygonsSegmentIndex>::const_iterator;
|
||||
using LinesIt = Lines::iterator;
|
||||
using ColoredLinesConstIt = ColoredLines::const_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 &, ColoredLinesConstIt, ColoredLinesConstIt);
|
||||
template bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &, PolygonsSegmentIndexConstIt, PolygonsSegmentIndexConstIt);
|
||||
// The tangent vector of the parabola is computed based on the Proof of the reflective property.
|
||||
// https://en.wikipedia.org/wiki/Parabola#Proof_of_the_reflective_property
|
||||
// https://math.stackexchange.com/q/2439647/2439663#comment5039739_2439663
|
||||
@@ -117,30 +127,30 @@ using ParabolicTangentToSegmentOrientation = impl::ParabolicTangentToSegmentOrie
|
||||
using ParabolicTangentToParabolicTangentOrientation = impl::ParabolicTangentToParabolicTangentOrientationPredicateFiltered;
|
||||
using CGAL_Point = impl::K::Point_2;
|
||||
|
||||
inline static CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
|
||||
inline static CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
|
||||
inline static CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
|
||||
inline CGAL_Point to_cgal_point(const VD::vertex_type *pt) { return {pt->x(), pt->y()}; }
|
||||
inline CGAL_Point to_cgal_point(const Point &pt) { return {pt.x(), pt.y()}; }
|
||||
inline CGAL_Point to_cgal_point(const Vec2d &pt) { return {pt.x(), pt.y()}; }
|
||||
|
||||
inline static Linef make_linef(const VD::edge_type &edge)
|
||||
inline Linef make_linef(const VD::edge_type &edge)
|
||||
{
|
||||
const VD::vertex_type *v0 = edge.vertex0();
|
||||
const VD::vertex_type *v1 = edge.vertex1();
|
||||
return {Vec2d(v0->x(), v0->y()), Vec2d(v1->x(), v1->y())};
|
||||
}
|
||||
|
||||
[[maybe_unused]] inline static bool is_equal(const VD::vertex_type &first, const VD::vertex_type &second) { return first.x() == second.x() && first.y() == second.y(); }
|
||||
[[maybe_unused]] inline bool is_equal(const VD::vertex_type &vertex_first, const VD::vertex_type &vertex_second) { return vertex_first.x() == vertex_second.x() && vertex_first.y() == vertex_second.y(); }
|
||||
|
||||
// FIXME Lukas H.: Also includes parabolic segments.
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_diagram)
|
||||
{
|
||||
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;
|
||||
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_Point { return {pt.x(), pt.y()}; };
|
||||
using CGAL_E_Point = CGAL::Exact_predicates_exact_constructions_kernel::Point_2;
|
||||
using CGAL_E_Segment = CGAL::Arr_segment_traits_2<CGAL::Exact_predicates_exact_constructions_kernel>::Curve_2;
|
||||
auto to_cgal_point = [](const VD::vertex_type &pt) -> CGAL_E_Point { return {pt.x(), pt.y()}; };
|
||||
|
||||
assert(std::all_of(voronoi_diagram.edges().cbegin(), voronoi_diagram.edges().cend(),
|
||||
[](const VD::edge_type &edge) { return edge.color() == 0; }));
|
||||
|
||||
std::vector<CGAL_Segment> segments;
|
||||
std::vector<CGAL_E_Segment> segments;
|
||||
segments.reserve(voronoi_diagram.num_edges());
|
||||
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges()) {
|
||||
@@ -159,7 +169,7 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(const VD &voronoi_
|
||||
for (const VD::edge_type &edge : voronoi_diagram.edges())
|
||||
edge.color(0);
|
||||
|
||||
std::vector<CGAL_Point> intersections_pt;
|
||||
std::vector<CGAL_E_Point> intersections_pt;
|
||||
CGAL::compute_intersection_points(segments.begin(), segments.end(), std::back_inserter(intersections_pt));
|
||||
return intersections_pt.empty();
|
||||
}
|
||||
@@ -174,29 +184,43 @@ struct ParabolicSegment
|
||||
const CGAL::Orientation is_focus_on_left;
|
||||
};
|
||||
|
||||
inline static ParabolicSegment get_parabolic_segment(const VD::edge_type &edge, const std::vector<VoronoiUtils::Segment> &segments)
|
||||
template<typename SegmentIterator>
|
||||
inline 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,
|
||||
ParabolicSegment>::type
|
||||
get_parabolic_segment(const VD::edge_type &edge, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
using Segment = typename std::iterator_traits<SegmentIterator>::value_type;
|
||||
assert(edge.is_curved());
|
||||
|
||||
const VD::cell_type *left_cell = edge.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 VoronoiUtils::Segment &directrix = VoronoiUtils::getSourceSegment(*(left_cell->contains_point() ? right_cell : left_cell), segments);
|
||||
const Point focus_pt = VoronoiUtils::get_source_point(*(left_cell->contains_point() ? left_cell : right_cell), segment_begin, segment_end);
|
||||
const Segment &directrix = VoronoiUtils::get_source_segment(*(left_cell->contains_point() ? right_cell : left_cell), segment_begin, segment_end);
|
||||
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);
|
||||
return {focus_pt, Line(directrix.from(), directrix.to()), make_linef(edge), focus_side};
|
||||
const Point directrix_from = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::LOW);
|
||||
const Point directrix_to = boost::polygon::segment_traits<Segment>::get(directrix, boost::polygon::HIGH);
|
||||
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) {
|
||||
template<typename SegmentIterator>
|
||||
inline 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,
|
||||
CGAL::Orientation>::type
|
||||
orientation_of_two_edges(const VD::edge_type &edge_a, const VD::edge_type &edge_b, const SegmentIterator segment_begin, const SegmentIterator segment_end)
|
||||
{
|
||||
assert(is_equal(*edge_a.vertex0(), *edge_b.vertex0()));
|
||||
CGAL::Orientation orientation;
|
||||
if (edge_a.is_linear() && edge_b.is_linear()) {
|
||||
orientation = CGAL::orientation(to_cgal_point(edge_a.vertex0()), to_cgal_point(edge_a.vertex1()), to_cgal_point(edge_b.vertex1()));
|
||||
} else if (edge_a.is_curved() && edge_b.is_curved()) {
|
||||
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segments);
|
||||
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segments);
|
||||
const ParabolicSegment parabolic_a = get_parabolic_segment(edge_a, segment_begin, segment_end);
|
||||
const ParabolicSegment parabolic_b = get_parabolic_segment(edge_b, segment_begin, segment_end);
|
||||
orientation = ParabolicTangentToParabolicTangentOrientation{}(to_cgal_point(parabolic_a.segment.a),
|
||||
to_cgal_point(parabolic_a.focus),
|
||||
to_cgal_point(parabolic_a.directrix.a),
|
||||
@@ -212,7 +236,7 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
|
||||
|
||||
const VD::edge_type &linear_edge = edge_a.is_curved() ? edge_b : edge_a;
|
||||
const VD::edge_type ¶bolic_edge = edge_a.is_curved() ? edge_a : edge_b;
|
||||
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segments);
|
||||
const ParabolicSegment parabolic = get_parabolic_segment(parabolic_edge, segment_begin, segment_end);
|
||||
orientation = ParabolicTangentToSegmentOrientation{}(to_cgal_point(parabolic.segment.a), to_cgal_point(linear_edge.vertex1()),
|
||||
to_cgal_point(parabolic.focus),
|
||||
to_cgal_point(parabolic.directrix.a),
|
||||
@@ -226,39 +250,54 @@ inline static CGAL::Orientation orientation_of_two_edges(const VD::edge_type &ed
|
||||
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)
|
||||
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,
|
||||
bool>::type
|
||||
check_if_three_edges_are_ccw(const VD::edge_type &edge_first,
|
||||
const VD::edge_type &edge_second,
|
||||
const VD::edge_type &edge_third,
|
||||
const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end)
|
||||
{
|
||||
assert(is_equal(*first.vertex0(), *second.vertex0()) && is_equal(*second.vertex0(), *third.vertex0()));
|
||||
assert(is_equal(*edge_first.vertex0(), *edge_second.vertex0()) && is_equal(*edge_second.vertex0(), *edge_third.vertex0()));
|
||||
|
||||
CGAL::Orientation orientation = orientation_of_two_edges(first, second, segments);
|
||||
CGAL::Orientation orientation = orientation_of_two_edges(edge_first, edge_second, segment_begin, segment_end);
|
||||
if (orientation == CGAL::Orientation::COLLINEAR) {
|
||||
// The first two edges are collinear, so the third edge must be on the right side on the first of them.
|
||||
return orientation_of_two_edges(first, third, segments) == CGAL::Orientation::RIGHT_TURN;
|
||||
return orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end) == CGAL::Orientation::RIGHT_TURN;
|
||||
} else if (orientation == CGAL::Orientation::LEFT_TURN) {
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is bellow PI.
|
||||
// So we need to check if test_pt isn't between them.
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
|
||||
return (orientation1 != CGAL::Orientation::LEFT_TURN || orientation2 != CGAL::Orientation::RIGHT_TURN);
|
||||
} else {
|
||||
assert(orientation == CGAL::Orientation::RIGHT_TURN);
|
||||
// CCW oriented angle between vectors (common_pt, pt1) and (common_pt, pt2) is upper PI.
|
||||
// So we need to check if test_pt is between them.
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(first, third, segments);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(second, third, segments);
|
||||
CGAL::Orientation orientation1 = orientation_of_two_edges(edge_first, edge_third, segment_begin, segment_end);
|
||||
CGAL::Orientation orientation2 = orientation_of_two_edges(edge_second, edge_third, segment_begin, segment_end);
|
||||
return (orientation1 == CGAL::Orientation::RIGHT_TURN || orientation2 == CGAL::Orientation::LEFT_TURN);
|
||||
}
|
||||
}
|
||||
|
||||
bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<VoronoiUtils::Segment> &segments)
|
||||
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,
|
||||
bool>::type
|
||||
VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VD &voronoi_diagram,
|
||||
const SegmentIterator segment_begin,
|
||||
const SegmentIterator segment_end)
|
||||
{
|
||||
for (const VD::vertex_type &vertex : voronoi_diagram.vertices()) {
|
||||
std::vector<const VD::edge_type *> edges;
|
||||
const VD::edge_type *edge = vertex.incident_edge();
|
||||
|
||||
do {
|
||||
if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr &&
|
||||
VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
if (edge->is_finite() && edge->vertex0() != nullptr && edge->vertex1() != nullptr && VoronoiUtils::is_finite(*edge->vertex0()) && VoronoiUtils::is_finite(*edge->vertex1()))
|
||||
edges.emplace_back(edge);
|
||||
|
||||
edge = edge->rot_next();
|
||||
@@ -267,11 +306,11 @@ bool VoronoiUtilsCgal::is_voronoi_diagram_planar_angle(const VoronoiDiagram &vor
|
||||
// Checking for CCW make sense for three and more edges.
|
||||
if (edges.size() > 2) {
|
||||
for (auto edge_it = edges.begin() ; edge_it != edges.end(); ++edge_it) {
|
||||
const Geometry::VoronoiDiagram::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
|
||||
const Geometry::VoronoiDiagram::edge_type *curr_edge = *edge_it;
|
||||
const Geometry::VoronoiDiagram::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
|
||||
const VD::edge_type *prev_edge = edge_it == edges.begin() ? edges.back() : *std::prev(edge_it);
|
||||
const VD::edge_type *curr_edge = *edge_it;
|
||||
const VD::edge_type *next_edge = std::next(edge_it) == edges.end() ? edges.front() : *std::next(edge_it);
|
||||
|
||||
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segments))
|
||||
if (!check_if_three_edges_are_ccw(*prev_edge, *curr_edge, *next_edge, segment_begin, segment_end))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#define slic3r_VoronoiUtilsCgal_hpp_
|
||||
|
||||
#include "Voronoi.hpp"
|
||||
#include "../Arachne/utils/VoronoiUtils.hpp"
|
||||
#include "../Arachne/utils/PolygonsSegmentIndex.hpp"
|
||||
|
||||
namespace Slic3r::Geometry {
|
||||
class VoronoiDiagram;
|
||||
@@ -14,7 +14,12 @@ public:
|
||||
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.
|
||||
static bool is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, const std::vector<Arachne::VoronoiUtils::Segment> &segments);
|
||||
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,
|
||||
bool>::type
|
||||
is_voronoi_diagram_planar_angle(const VoronoiDiagram &voronoi_diagram, SegmentIterator segment_begin, SegmentIterator segment_end);
|
||||
|
||||
};
|
||||
} // namespace Slic3r::Geometry
|
||||
|
||||
Reference in New Issue
Block a user