2129 lines
92 KiB
C++
2129 lines
92 KiB
C++
|
//Copyright (c) 2021 Ultimaker B.V.
|
||
|
//CuraEngine is released under the terms of the AGPLv3 or higher.
|
||
|
|
||
|
#include "SkeletalTrapezoidation.hpp"
|
||
|
|
||
|
#include <stack>
|
||
|
#include <functional>
|
||
|
#include <sstream>
|
||
|
#include <queue>
|
||
|
#include <functional>
|
||
|
#include <boost/log/trivial.hpp>
|
||
|
|
||
|
#include "utils/linearAlg2D.hpp"
|
||
|
#include "Utils.hpp"
|
||
|
#include "SVG.hpp"
|
||
|
#include "Geometry/VoronoiVisualUtils.hpp"
|
||
|
#include "Geometry/VoronoiUtilsCgal.hpp"
|
||
|
#include "../EdgeGrid.hpp"
|
||
|
|
||
|
#include "Geometry/VoronoiUtils.hpp"
|
||
|
|
||
|
#define SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX 1000 //A limit to how long it'll keep searching for adjacent beads. Increasing will re-use beadings more often (saving performance), but search longer for beading (costing performance).
|
||
|
|
||
|
namespace Slic3r::Arachne
|
||
|
{
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
static void export_graph_to_svg(const std::string &path,
|
||
|
SkeletalTrapezoidationGraph &graph,
|
||
|
const Polygons &polys,
|
||
|
const std::vector<std::shared_ptr<LineJunctions>> &edge_junctions = {},
|
||
|
const bool beat_count = true,
|
||
|
const bool transition_middles = true,
|
||
|
const bool transition_ends = true)
|
||
|
{
|
||
|
const std::vector<std::string> colors = {"blue", "cyan", "red", "orange", "magenta", "pink", "purple", "green", "yellow"};
|
||
|
coordf_t stroke_width = scale_(0.03);
|
||
|
BoundingBox bbox = get_extents(polys);
|
||
|
for (const auto &node : graph.nodes)
|
||
|
bbox.merge(node.p);
|
||
|
|
||
|
bbox.offset(scale_(1.));
|
||
|
|
||
|
::Slic3r::SVG svg(path.c_str(), bbox);
|
||
|
for (const auto &line : to_lines(polys))
|
||
|
svg.draw(line, "gray", stroke_width);
|
||
|
|
||
|
for (const auto &edge : graph.edges)
|
||
|
svg.draw(Line(edge.from->p, edge.to->p), (edge.data.centralIsSet() && edge.data.isCentral()) ? "blue" : "cyan", stroke_width);
|
||
|
|
||
|
for (const auto &line_junction : edge_junctions)
|
||
|
for (const auto &extrusion_junction : *line_junction)
|
||
|
svg.draw(extrusion_junction.p, "orange", coord_t(stroke_width * 2.));
|
||
|
|
||
|
if (beat_count) {
|
||
|
for (const auto &node : graph.nodes) {
|
||
|
svg.draw(node.p, "red", coord_t(stroke_width * 1.6));
|
||
|
svg.draw_text(node.p, std::to_string(node.data.bead_count).c_str(), "black", 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (transition_middles) {
|
||
|
for (auto &edge : graph.edges) {
|
||
|
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionMiddle>> transitions = edge.data.getTransitions(); transitions) {
|
||
|
for (auto &transition : *transitions) {
|
||
|
Line edge_line = Line(edge.to->p, edge.from->p);
|
||
|
double edge_length = edge_line.length();
|
||
|
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
|
||
|
svg.draw(pt, "magenta", coord_t(stroke_width * 1.5));
|
||
|
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (transition_ends) {
|
||
|
for (auto &edge : graph.edges) {
|
||
|
if (std::shared_ptr<std::list<SkeletalTrapezoidationEdge::TransitionEnd>> transitions = edge.data.getTransitionEnds(); transitions) {
|
||
|
for (auto &transition : *transitions) {
|
||
|
Line edge_line = Line(edge.to->p, edge.from->p);
|
||
|
double edge_length = edge_line.length();
|
||
|
Point pt = edge_line.a + (edge_line.vector().cast<double>() * (double(transition.pos) / edge_length)).cast<coord_t>();
|
||
|
svg.draw(pt, transition.is_lower_end ? "green" : "lime", coord_t(stroke_width * 1.5));
|
||
|
svg.draw_text(pt, std::to_string(transition.lower_bead_count).c_str(), "black", 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
SkeletalTrapezoidation::node_t &SkeletalTrapezoidation::makeNode(const VD::vertex_type &vd_node, Point p) {
|
||
|
auto he_node_it = vd_node_to_he_node.find(&vd_node);
|
||
|
if (he_node_it == vd_node_to_he_node.end())
|
||
|
{
|
||
|
graph.nodes.emplace_front(SkeletalTrapezoidationJoint(), p);
|
||
|
node_t& node = graph.nodes.front();
|
||
|
vd_node_to_he_node.emplace(&vd_node, &node);
|
||
|
return node;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return *he_node_it->second;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::transferEdge(Point from, Point to, const VD::edge_type &vd_edge, edge_t *&prev_edge, Point &start_source_point, Point &end_source_point, const std::vector<Segment> &segments) {
|
||
|
auto he_edge_it = vd_edge_to_he_edge.find(vd_edge.twin());
|
||
|
if (he_edge_it != vd_edge_to_he_edge.end())
|
||
|
{ // Twin segment(s) have already been made
|
||
|
edge_t* source_twin = he_edge_it->second;
|
||
|
assert(source_twin);
|
||
|
auto end_node_it = vd_node_to_he_node.find(vd_edge.vertex1());
|
||
|
assert(end_node_it != vd_node_to_he_node.end());
|
||
|
node_t* end_node = end_node_it->second;
|
||
|
for (edge_t* twin = source_twin; ;twin = twin->prev->twin->prev)
|
||
|
{
|
||
|
if(!twin)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Encountered a voronoi edge without twin.";
|
||
|
continue; //Prevent reading unallocated memory.
|
||
|
}
|
||
|
assert(twin);
|
||
|
graph.edges.emplace_front(SkeletalTrapezoidationEdge());
|
||
|
edge_t* edge = &graph.edges.front();
|
||
|
edge->from = twin->to;
|
||
|
edge->to = twin->from;
|
||
|
edge->twin = twin;
|
||
|
twin->twin = edge;
|
||
|
edge->from->incident_edge = edge;
|
||
|
|
||
|
if (prev_edge)
|
||
|
{
|
||
|
edge->prev = prev_edge;
|
||
|
prev_edge->next = edge;
|
||
|
}
|
||
|
|
||
|
prev_edge = edge;
|
||
|
|
||
|
if (prev_edge->to == end_node)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!twin->prev || !twin->prev->twin || !twin->prev->twin->prev)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(error) << "Discretized segment behaves oddly!";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
assert(twin->prev); // Forth rib
|
||
|
assert(twin->prev->twin); // Back rib
|
||
|
assert(twin->prev->twin->prev); // Prev segment along parabola
|
||
|
|
||
|
constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped
|
||
|
graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end);
|
||
|
}
|
||
|
assert(prev_edge);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Points discretized = discretize(vd_edge, segments);
|
||
|
assert(discretized.size() >= 2);
|
||
|
if(discretized.size() < 2)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Discretized Voronoi edge is degenerate.";
|
||
|
}
|
||
|
|
||
|
assert(!prev_edge || prev_edge->to);
|
||
|
if(prev_edge && !prev_edge->to)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Previous edge doesn't go anywhere.";
|
||
|
}
|
||
|
node_t* v0 = (prev_edge)? prev_edge->to : &makeNode(*vd_edge.vertex0(), from); // TODO: investigate whether boost:voronoi can produce multiple verts and violates consistency
|
||
|
Point p0 = discretized.front();
|
||
|
for (size_t p1_idx = 1; p1_idx < discretized.size(); p1_idx++)
|
||
|
{
|
||
|
Point p1 = discretized[p1_idx];
|
||
|
node_t* v1;
|
||
|
if (p1_idx < discretized.size() - 1)
|
||
|
{
|
||
|
graph.nodes.emplace_front(SkeletalTrapezoidationJoint(), p1);
|
||
|
v1 = &graph.nodes.front();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
v1 = &makeNode(*vd_edge.vertex1(), to);
|
||
|
}
|
||
|
|
||
|
graph.edges.emplace_front(SkeletalTrapezoidationEdge());
|
||
|
edge_t* edge = &graph.edges.front();
|
||
|
edge->from = v0;
|
||
|
edge->to = v1;
|
||
|
edge->from->incident_edge = edge;
|
||
|
|
||
|
if (prev_edge)
|
||
|
{
|
||
|
edge->prev = prev_edge;
|
||
|
prev_edge->next = edge;
|
||
|
}
|
||
|
|
||
|
prev_edge = edge;
|
||
|
p0 = p1;
|
||
|
v0 = v1;
|
||
|
|
||
|
if (p1_idx < discretized.size() - 1)
|
||
|
{ // Rib for last segment gets introduced outside this function!
|
||
|
constexpr bool is_not_next_to_start_or_end = false; // Only ribs at the end of a cell should be skipped
|
||
|
graph.makeRib(prev_edge, start_source_point, end_source_point, is_not_next_to_start_or_end);
|
||
|
}
|
||
|
}
|
||
|
assert(prev_edge);
|
||
|
vd_edge_to_he_edge.emplace(&vd_edge, prev_edge);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Points SkeletalTrapezoidation::discretize(const VD::edge_type& vd_edge, const std::vector<Segment>& segments)
|
||
|
{
|
||
|
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(vd_edge));
|
||
|
|
||
|
/*Terminology in this function assumes that the edge moves horizontally from
|
||
|
left to right. This is not necessarily the case; the edge can go in any
|
||
|
direction, but it helps to picture it in a certain direction in your head.*/
|
||
|
|
||
|
const VD::cell_type *left_cell = vd_edge.cell();
|
||
|
const VD::cell_type *right_cell = vd_edge.twin()->cell();
|
||
|
|
||
|
Point start = Geometry::VoronoiUtils::to_point(vd_edge.vertex0()).cast<coord_t>();
|
||
|
Point end = Geometry::VoronoiUtils::to_point(vd_edge.vertex1()).cast<coord_t>();
|
||
|
|
||
|
bool point_left = left_cell->contains_point();
|
||
|
bool point_right = right_cell->contains_point();
|
||
|
if ((!point_left && !point_right) || vd_edge.is_secondary()) // Source vert is directly connected to source segment
|
||
|
{
|
||
|
return Points({ start, end });
|
||
|
}
|
||
|
else if (point_left != point_right) //This is a parabolic edge between a point and a line.
|
||
|
{
|
||
|
Point p = Geometry::VoronoiUtils::get_source_point(*(point_left ? left_cell : right_cell), segments.begin(), segments.end());
|
||
|
const Segment& s = Geometry::VoronoiUtils::get_source_segment(*(point_left ? right_cell : left_cell), segments.begin(), segments.end());
|
||
|
return Geometry::VoronoiUtils::discretize_parabola(p, s, start, end, discretization_step_size, transitioning_angle);
|
||
|
}
|
||
|
else //This is a straight edge between two points.
|
||
|
{
|
||
|
/*While the edge is straight, it is still discretized since the part
|
||
|
becomes narrower between the two points. As such it may need different
|
||
|
beadings along the way.*/
|
||
|
Point left_point = Geometry::VoronoiUtils::get_source_point(*left_cell, segments.begin(), segments.end());
|
||
|
Point right_point = Geometry::VoronoiUtils::get_source_point(*right_cell, segments.begin(), segments.end());
|
||
|
coord_t d = (right_point - left_point).cast<int64_t>().norm();
|
||
|
Point middle = (left_point + right_point) / 2;
|
||
|
Point x_axis_dir = perp(Point(right_point - left_point));
|
||
|
coord_t x_axis_length = x_axis_dir.cast<int64_t>().norm();
|
||
|
|
||
|
const auto projected_x = [x_axis_dir, x_axis_length, middle](Point from) //Project a point on the edge.
|
||
|
{
|
||
|
Point vec = from - middle;
|
||
|
assert(( vec.cast<int64_t>().dot(x_axis_dir.cast<int64_t>())/ int64_t(x_axis_length)) <= std::numeric_limits<coord_t>::max());
|
||
|
coord_t x = vec.cast<int64_t>().dot(x_axis_dir.cast<int64_t>()) / int64_t(x_axis_length);
|
||
|
return x;
|
||
|
};
|
||
|
|
||
|
coord_t start_x = projected_x(start);
|
||
|
coord_t end_x = projected_x(end);
|
||
|
|
||
|
//Part of the edge will be bound to the markings on the endpoints of the edge. Calculate how far that is.
|
||
|
float bound = 0.5 / tan((M_PI - transitioning_angle) * 0.5);
|
||
|
int64_t marking_start_x = - int64_t(d) * bound;
|
||
|
int64_t marking_end_x = int64_t(d) * bound;
|
||
|
|
||
|
assert((middle.cast<int64_t>() + x_axis_dir.cast<int64_t>() * marking_start_x / int64_t(x_axis_length)).x() <= std::numeric_limits<coord_t>::max());
|
||
|
assert((middle.cast<int64_t>() + x_axis_dir.cast<int64_t>() * marking_start_x / int64_t(x_axis_length)).y() <= std::numeric_limits<coord_t>::max());
|
||
|
assert((middle.cast<int64_t>() + x_axis_dir.cast<int64_t>() * marking_end_x / int64_t(x_axis_length)).x() <= std::numeric_limits<coord_t>::max());
|
||
|
assert((middle.cast<int64_t>() + x_axis_dir.cast<int64_t>() * marking_end_x / int64_t(x_axis_length)).y() <= std::numeric_limits<coord_t>::max());
|
||
|
Point marking_start = middle + (x_axis_dir.cast<int64_t>() * marking_start_x / int64_t(x_axis_length)).cast<coord_t>();
|
||
|
Point marking_end = middle + (x_axis_dir.cast<int64_t>() * marking_end_x / int64_t(x_axis_length)).cast<coord_t>();
|
||
|
int64_t direction = 1;
|
||
|
|
||
|
if (start_x > end_x) //Oops, the Voronoi edge is the other way around.
|
||
|
{
|
||
|
direction = -1;
|
||
|
std::swap(marking_start, marking_end);
|
||
|
std::swap(marking_start_x, marking_end_x);
|
||
|
}
|
||
|
|
||
|
//Start generating points along the edge.
|
||
|
Point a = start;
|
||
|
Point b = end;
|
||
|
Points ret;
|
||
|
ret.emplace_back(a);
|
||
|
|
||
|
//Introduce an extra edge at the borders of the markings?
|
||
|
bool add_marking_start = marking_start_x * direction > int64_t(start_x) * direction;
|
||
|
bool add_marking_end = marking_end_x * direction > int64_t(start_x) * direction;
|
||
|
|
||
|
//The edge's length may not be divisible by the step size, so calculate an integer step count and evenly distribute the vertices among those.
|
||
|
Point ab = b - a;
|
||
|
coord_t ab_size = ab.cast<int64_t>().norm();
|
||
|
coord_t step_count = (ab_size + discretization_step_size / 2) / discretization_step_size;
|
||
|
if (step_count % 2 == 1)
|
||
|
{
|
||
|
step_count++; // enforce a discretization point being added in the middle
|
||
|
}
|
||
|
for (coord_t step = 1; step < step_count; step++)
|
||
|
{
|
||
|
Point here = a + (ab.cast<int64_t>() * int64_t(step) / int64_t(step_count)).cast<coord_t>(); //Now simply interpolate the coordinates to get the new vertices!
|
||
|
coord_t x_here = projected_x(here); //If we've surpassed the position of the extra markings, we may need to insert them first.
|
||
|
if (add_marking_start && marking_start_x * direction < int64_t(x_here) * direction)
|
||
|
{
|
||
|
ret.emplace_back(marking_start);
|
||
|
add_marking_start = false;
|
||
|
}
|
||
|
if (add_marking_end && marking_end_x * direction < int64_t(x_here) * direction)
|
||
|
{
|
||
|
ret.emplace_back(marking_end);
|
||
|
add_marking_end = false;
|
||
|
}
|
||
|
ret.emplace_back(here);
|
||
|
}
|
||
|
if (add_marking_end && marking_end_x * direction < int64_t(end_x) * direction)
|
||
|
{
|
||
|
ret.emplace_back(marking_end);
|
||
|
}
|
||
|
ret.emplace_back(b);
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool SkeletalTrapezoidation::computePointCellRange(const VD::cell_type &cell, Point &start_source_point, Point &end_source_point, const VD::edge_type *&starting_vd_edge, const VD::edge_type *&ending_vd_edge, const std::vector<Segment> &segments) {
|
||
|
if (cell.incident_edge()->is_infinite())
|
||
|
return false; //Infinite edges only occur outside of the polygon. Don't copy any part of this cell.
|
||
|
|
||
|
// Check if any point of the cell is inside or outside polygon
|
||
|
// Copy whole cell into graph or not at all
|
||
|
|
||
|
// If the cell.incident_edge()->vertex0() is far away so much that it doesn't even fit into Vec2i64, then there is no way that it will be inside the input polygon.
|
||
|
if (const VD::vertex_type &vert = *cell.incident_edge()->vertex0();
|
||
|
vert.x() >= double(std::numeric_limits<int64_t>::max()) || vert.x() <= double(std::numeric_limits<int64_t>::lowest()) ||
|
||
|
vert.y() >= double(std::numeric_limits<int64_t>::max()) || vert.y() <= double(std::numeric_limits<int64_t>::lowest()))
|
||
|
return false; // Don't copy any part of this cell
|
||
|
|
||
|
const Point source_point = Geometry::VoronoiUtils::get_source_point(cell, segments.begin(), segments.end());
|
||
|
const PolygonsPointIndex source_point_index = Geometry::VoronoiUtils::get_source_point_index(cell, segments.begin(), segments.end());
|
||
|
Vec2i64 some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex0());
|
||
|
if (some_point == source_point.cast<int64_t>())
|
||
|
some_point = Geometry::VoronoiUtils::to_point(cell.incident_edge()->vertex1());
|
||
|
|
||
|
//Test if the some_point is even inside the polygon.
|
||
|
//The edge leading out of a polygon must have an endpoint that's not in the corner following the contour of the polygon at that vertex.
|
||
|
//So if it's inside the corner formed by the polygon vertex, it's all fine.
|
||
|
//But if it's outside of the corner, it must be a vertex of the Voronoi diagram that goes outside of the polygon towards infinity.
|
||
|
if (!LinearAlg2D::isInsideCorner(source_point_index.prev().p(), source_point_index.p(), source_point_index.next().p(), some_point))
|
||
|
return false; // Don't copy any part of this cell
|
||
|
|
||
|
const VD::edge_type* vd_edge = cell.incident_edge();
|
||
|
do {
|
||
|
assert(vd_edge->is_finite());
|
||
|
if (Vec2i64 p1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()); p1 == source_point.cast<int64_t>()) {
|
||
|
start_source_point = source_point;
|
||
|
end_source_point = source_point;
|
||
|
starting_vd_edge = vd_edge->next();
|
||
|
ending_vd_edge = vd_edge;
|
||
|
} else {
|
||
|
assert((Geometry::VoronoiUtils::to_point(vd_edge->vertex0()) == source_point.cast<int64_t>() || !vd_edge->is_secondary()) && "point cells must end in the point! They cannot cross the point with an edge, because collinear edges are not allowed in the input.");
|
||
|
}
|
||
|
}
|
||
|
while (vd_edge = vd_edge->next(), vd_edge != cell.incident_edge());
|
||
|
assert(starting_vd_edge && ending_vd_edge);
|
||
|
assert(starting_vd_edge != ending_vd_edge);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
SkeletalTrapezoidation::SkeletalTrapezoidation(const Polygons& polys, const BeadingStrategy& beading_strategy,
|
||
|
double transitioning_angle, coord_t discretization_step_size,
|
||
|
coord_t transition_filter_dist, coord_t allowed_filter_deviation,
|
||
|
coord_t beading_propagation_transition_dist
|
||
|
): transitioning_angle(transitioning_angle),
|
||
|
discretization_step_size(discretization_step_size),
|
||
|
transition_filter_dist(transition_filter_dist),
|
||
|
allowed_filter_deviation(allowed_filter_deviation),
|
||
|
beading_propagation_transition_dist(beading_propagation_transition_dist),
|
||
|
beading_strategy(beading_strategy)
|
||
|
{
|
||
|
constructFromPolygons(polys);
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::constructFromPolygons(const Polygons& polys)
|
||
|
{
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
this->outline = polys;
|
||
|
#endif
|
||
|
|
||
|
// Check self intersections.
|
||
|
assert([&polys]() -> bool {
|
||
|
EdgeGrid::Grid grid;
|
||
|
grid.set_bbox(get_extents(polys));
|
||
|
grid.create(polys, scaled<coord_t>(10.));
|
||
|
return !grid.has_intersecting_edges();
|
||
|
}());
|
||
|
|
||
|
vd_edge_to_he_edge.clear();
|
||
|
vd_node_to_he_node.clear();
|
||
|
|
||
|
std::vector<Segment> segments;
|
||
|
for (size_t poly_idx = 0; poly_idx < polys.size(); poly_idx++)
|
||
|
for (size_t point_idx = 0; point_idx < polys[poly_idx].size(); point_idx++)
|
||
|
segments.emplace_back(&polys, poly_idx, point_idx);
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
{
|
||
|
static int iRun = 0;
|
||
|
BoundingBox bbox = get_extents(polys);
|
||
|
SVG svg(debug_out_path("arachne_voronoi-input-%d.svg", iRun++).c_str(), bbox);
|
||
|
svg.draw_outline(polys, "black", scaled<coordf_t>(0.03f));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
VD voronoi_diagram;
|
||
|
voronoi_diagram.construct_voronoi(segments.cbegin(), segments.cend());
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG_VORONOI
|
||
|
{
|
||
|
static int iRun = 0;
|
||
|
dump_voronoi_to_svg(debug_out_path("arachne_voronoi-diagram-%d.svg", iRun++).c_str(), voronoi_diagram, to_points(polys), to_lines(polys));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
assert(this->graph.edges.empty() && this->graph.nodes.empty() && this->vd_edge_to_he_edge.empty() && this->vd_node_to_he_node.empty());
|
||
|
for (const VD::cell_type &cell : voronoi_diagram.cells()) {
|
||
|
if (!cell.incident_edge())
|
||
|
continue; // There is no spoon
|
||
|
|
||
|
Point start_source_point;
|
||
|
Point end_source_point;
|
||
|
const VD::edge_type *starting_voronoi_edge = nullptr;
|
||
|
const VD::edge_type *ending_voronoi_edge = nullptr;
|
||
|
// Compute and store result in above variables
|
||
|
|
||
|
if (cell.contains_point()) {
|
||
|
const bool keep_going = computePointCellRange(cell, start_source_point, end_source_point, starting_voronoi_edge, ending_voronoi_edge, segments);
|
||
|
if (!keep_going)
|
||
|
continue;
|
||
|
} else {
|
||
|
assert(cell.contains_segment());
|
||
|
Geometry::SegmentCellRange<Point> cell_range = Geometry::VoronoiUtils::compute_segment_cell_range(cell, segments.cbegin(), segments.cend());
|
||
|
assert(cell_range.is_valid());
|
||
|
start_source_point = cell_range.segment_start_point;
|
||
|
end_source_point = cell_range.segment_end_point;
|
||
|
starting_voronoi_edge = cell_range.edge_begin;
|
||
|
ending_voronoi_edge = cell_range.edge_end;
|
||
|
}
|
||
|
|
||
|
if (!starting_voronoi_edge || !ending_voronoi_edge) {
|
||
|
assert(false && "Each cell should start / end in a polygon vertex");
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Copy start to end edge to graph
|
||
|
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*starting_voronoi_edge));
|
||
|
edge_t *prev_edge = nullptr;
|
||
|
transferEdge(start_source_point, Geometry::VoronoiUtils::to_point(starting_voronoi_edge->vertex1()).cast<coord_t>(), *starting_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||
|
node_t *starting_node = vd_node_to_he_node[starting_voronoi_edge->vertex0()];
|
||
|
starting_node->data.distance_to_boundary = 0;
|
||
|
|
||
|
constexpr bool is_next_to_start_or_end = true;
|
||
|
graph.makeRib(prev_edge, start_source_point, end_source_point, is_next_to_start_or_end);
|
||
|
for (const VD::edge_type* vd_edge = starting_voronoi_edge->next(); vd_edge != ending_voronoi_edge; vd_edge = vd_edge->next()) {
|
||
|
assert(vd_edge->is_finite());
|
||
|
assert(Geometry::VoronoiUtils::is_in_range<coord_t>(*vd_edge));
|
||
|
|
||
|
Point v1 = Geometry::VoronoiUtils::to_point(vd_edge->vertex0()).cast<coord_t>();
|
||
|
Point v2 = Geometry::VoronoiUtils::to_point(vd_edge->vertex1()).cast<coord_t>();
|
||
|
transferEdge(v1, v2, *vd_edge, prev_edge, start_source_point, end_source_point, segments);
|
||
|
graph.makeRib(prev_edge, start_source_point, end_source_point, vd_edge->next() == ending_voronoi_edge);
|
||
|
}
|
||
|
|
||
|
transferEdge(Geometry::VoronoiUtils::to_point(ending_voronoi_edge->vertex0()).cast<coord_t>(), end_source_point, *ending_voronoi_edge, prev_edge, start_source_point, end_source_point, segments);
|
||
|
prev_edge->to->data.distance_to_boundary = 0;
|
||
|
}
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
assert(Geometry::VoronoiUtilsCgal::is_voronoi_diagram_planar_intersection(voronoi_diagram));
|
||
|
#endif
|
||
|
|
||
|
separatePointyQuadEndNodes();
|
||
|
|
||
|
graph.collapseSmallEdges();
|
||
|
|
||
|
// Set [incident_edge] the first possible edge that way we can iterate over all reachable edges from node.incident_edge,
|
||
|
// without needing to iterate backward
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
if (!edge.prev)
|
||
|
edge.from->incident_edge = &edge;
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::separatePointyQuadEndNodes()
|
||
|
{
|
||
|
NodeSet visited_nodes;
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (edge.prev)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
edge_t* quad_start = &edge;
|
||
|
if (visited_nodes.find(quad_start->from) == visited_nodes.end())
|
||
|
{
|
||
|
visited_nodes.emplace(quad_start->from);
|
||
|
}
|
||
|
else
|
||
|
{ // Needs to be duplicated
|
||
|
graph.nodes.emplace_back(*quad_start->from);
|
||
|
node_t* new_node = &graph.nodes.back();
|
||
|
new_node->incident_edge = quad_start;
|
||
|
quad_start->from = new_node;
|
||
|
quad_start->twin->to = new_node;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ^^^^^^^^^^^^^^^^^^^^^
|
||
|
// INITIALIZATION
|
||
|
// =====================
|
||
|
//
|
||
|
// =====================
|
||
|
// TRANSTISIONING
|
||
|
// vvvvvvvvvvvvvvvvvvvvv
|
||
|
//
|
||
|
|
||
|
void SkeletalTrapezoidation::generateToolpaths(std::vector<VariableWidthLines> &generated_toolpaths, bool filter_outermost_central_edges)
|
||
|
{
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
static int iRun = 0;
|
||
|
#endif
|
||
|
|
||
|
p_generated_toolpaths = &generated_toolpaths;
|
||
|
|
||
|
updateIsCentral();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-updateIsCentral-final-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
filterCentral(central_filter_dist);
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-filterCentral-final-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
if (filter_outermost_central_edges)
|
||
|
filterOuterCentral();
|
||
|
|
||
|
updateBeadCount();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-updateBeadCount-final-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
filterNoncentralRegions();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-filterNoncentralRegions-final-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
generateTransitioningRibs();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-final-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
generateExtraRibs();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-generateExtraRibs-final-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
generateSegments();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-generateSegments-final-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
++iRun;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::updateIsCentral()
|
||
|
{
|
||
|
// _.-'^` A and B are the endpoints of an edge we're checking.
|
||
|
// _.-'^` Part of the line AB will be used as a cap,
|
||
|
// _.-'^` \ because the polygon is too narrow there.
|
||
|
// _.-'^` \ If |AB| minus the cap is still bigger than dR,
|
||
|
// _.-'^` \ R2 the edge AB is considered central. It's then
|
||
|
// _.-'^` \ _.-'\`\ significant compared to the edges around it.
|
||
|
// _.-'^` \R1 _.-'^` '`\ dR
|
||
|
// _.-'^`a/2 \_.-'^`a \ Line AR2 is parallel to the polygon contour.
|
||
|
// `^'-._````````````````A```````````v````````B``````` dR is the remaining diameter at B.
|
||
|
// `^'-._ dD = |AB| As a result, AB is less often central if the polygon
|
||
|
// `^'-._ corner is obtuse.
|
||
|
// sin a = dR / dD
|
||
|
|
||
|
coord_t outer_edge_filter_length = beading_strategy.getTransitionThickness(0) / 2;
|
||
|
|
||
|
float cap = sin(beading_strategy.getTransitioningAngle() * 0.5); // = cos(bisector_angle / 2)
|
||
|
for (edge_t& edge: graph.edges)
|
||
|
{
|
||
|
assert(edge.twin);
|
||
|
if(!edge.twin)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Encountered a Voronoi edge without twin!";
|
||
|
continue;
|
||
|
}
|
||
|
if(edge.twin->data.centralIsSet())
|
||
|
{
|
||
|
edge.data.setIsCentral(edge.twin->data.isCentral());
|
||
|
}
|
||
|
else if(edge.data.type == SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD)
|
||
|
{
|
||
|
edge.data.setIsCentral(false);
|
||
|
}
|
||
|
else if(std::max(edge.from->data.distance_to_boundary, edge.to->data.distance_to_boundary) < outer_edge_filter_length)
|
||
|
{
|
||
|
edge.data.setIsCentral(false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Point a = edge.from->p;
|
||
|
Point b = edge.to->p;
|
||
|
Point ab = b - a;
|
||
|
coord_t dR = std::abs(edge.to->data.distance_to_boundary - edge.from->data.distance_to_boundary);
|
||
|
coord_t dD = ab.cast<int64_t>().norm();
|
||
|
edge.data.setIsCentral(dR < dD * cap);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::filterCentral(coord_t max_length)
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (isEndOfCentral(edge) && edge.to->isLocalMaximum() && !edge.to->isLocalMaximum())
|
||
|
{
|
||
|
filterCentral(edge.twin, 0, max_length);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool SkeletalTrapezoidation::filterCentral(edge_t* starting_edge, coord_t traveled_dist, coord_t max_length)
|
||
|
{
|
||
|
coord_t length = (starting_edge->from->p - starting_edge->to->p).cast<int64_t>().norm();
|
||
|
if (traveled_dist + length > max_length)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool should_dissolve = true; //Should we unmark this as central and propagate that?
|
||
|
for (edge_t* next_edge = starting_edge->next; next_edge && next_edge != starting_edge->twin; next_edge = next_edge->twin->next)
|
||
|
{
|
||
|
if (next_edge->data.isCentral())
|
||
|
{
|
||
|
should_dissolve &= filterCentral(next_edge, traveled_dist + length, max_length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
should_dissolve &= !starting_edge->to->isLocalMaximum(); // Don't filter central regions with a local maximum!
|
||
|
if (should_dissolve)
|
||
|
{
|
||
|
starting_edge->data.setIsCentral(false);
|
||
|
starting_edge->twin->data.setIsCentral(false);
|
||
|
}
|
||
|
return should_dissolve;
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::filterOuterCentral()
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (!edge.prev)
|
||
|
{
|
||
|
edge.data.setIsCentral(false);
|
||
|
edge.twin->data.setIsCentral(false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::updateBeadCount()
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (edge.data.isCentral())
|
||
|
{
|
||
|
edge.to->data.bead_count = beading_strategy.getOptimalBeadCount(edge.to->data.distance_to_boundary * 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Fix bead count at locally maximal R, also for central regions!! See TODO s in generateTransitionEnd(.)
|
||
|
for (node_t& node : graph.nodes)
|
||
|
{
|
||
|
if (node.isLocalMaximum())
|
||
|
{
|
||
|
if (node.data.distance_to_boundary < 0)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Distance to boundary not yet computed for local maximum!";
|
||
|
node.data.distance_to_boundary = std::numeric_limits<coord_t>::max();
|
||
|
edge_t* edge = node.incident_edge;
|
||
|
do
|
||
|
{
|
||
|
node.data.distance_to_boundary = std::min(node.data.distance_to_boundary, edge->to->data.distance_to_boundary + coord_t((edge->from->p - edge->to->p).cast<int64_t>().norm()));
|
||
|
} while (edge = edge->twin->next, edge != node.incident_edge);
|
||
|
}
|
||
|
coord_t bead_count = beading_strategy.getOptimalBeadCount(node.data.distance_to_boundary * 2);
|
||
|
node.data.bead_count = bead_count;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::filterNoncentralRegions()
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (!isEndOfCentral(edge))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
if(edge.to->data.bead_count < 0 && edge.to->data.distance_to_boundary != 0)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Encountered an uninitialized bead at the boundary!";
|
||
|
}
|
||
|
assert(edge.to->data.bead_count >= 0 || edge.to->data.distance_to_boundary == 0);
|
||
|
constexpr coord_t max_dist = scaled<coord_t>(0.4);
|
||
|
filterNoncentralRegions(&edge, edge.to->data.bead_count, 0, max_dist);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool SkeletalTrapezoidation::filterNoncentralRegions(edge_t* to_edge, coord_t bead_count, coord_t traveled_dist, coord_t max_dist)
|
||
|
{
|
||
|
coord_t r = to_edge->to->data.distance_to_boundary;
|
||
|
|
||
|
edge_t* next_edge = to_edge->next;
|
||
|
for (; next_edge && next_edge != to_edge->twin; next_edge = next_edge->twin->next)
|
||
|
{
|
||
|
if (next_edge->to->data.distance_to_boundary >= r || shorter_then(next_edge->to->p - next_edge->from->p, scaled<coord_t>(0.01)))
|
||
|
{
|
||
|
break; // Only walk upward
|
||
|
}
|
||
|
}
|
||
|
if (next_edge == to_edge->twin || ! next_edge)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const coord_t length = (next_edge->to->p - next_edge->from->p).cast<int64_t>().norm();
|
||
|
|
||
|
bool dissolve = false;
|
||
|
if (next_edge->to->data.bead_count == bead_count)
|
||
|
{
|
||
|
dissolve = true;
|
||
|
}
|
||
|
else if (next_edge->to->data.bead_count < 0)
|
||
|
{
|
||
|
dissolve = filterNoncentralRegions(next_edge, bead_count, traveled_dist + length, max_dist);
|
||
|
}
|
||
|
else // Upward bead count is different
|
||
|
{
|
||
|
// Dissolve if two central regions with different bead count are closer together than the max_dist (= transition distance)
|
||
|
dissolve = (traveled_dist + length < max_dist) && std::abs(next_edge->to->data.bead_count - bead_count) == 1;
|
||
|
}
|
||
|
|
||
|
if (dissolve)
|
||
|
{
|
||
|
next_edge->data.setIsCentral(true);
|
||
|
next_edge->twin->data.setIsCentral(true);
|
||
|
next_edge->to->data.bead_count = beading_strategy.getOptimalBeadCount(next_edge->to->data.distance_to_boundary * 2);
|
||
|
next_edge->to->data.transition_ratio = 0;
|
||
|
}
|
||
|
return dissolve; // Dissolving only depend on the one edge going upward. There cannot be multiple edges going upward.
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::generateTransitioningRibs()
|
||
|
{
|
||
|
// Store the upward edges to the transitions.
|
||
|
// We only store the halfedge for which the distance_to_boundary is higher at the end than at the beginning.
|
||
|
ptr_vector_t<std::list<TransitionMiddle>> edge_transitions;
|
||
|
generateTransitionMids(edge_transitions);
|
||
|
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{ // Check if there is a transition in between nodes with different bead counts
|
||
|
if (edge.data.isCentral() && edge.from->data.bead_count != edge.to->data.bead_count)
|
||
|
{
|
||
|
assert(edge.data.hasTransitions() || edge.twin->data.hasTransitions());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
filterTransitionMids();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
static int iRun = 0;
|
||
|
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-mids-%d.svg", iRun++), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
ptr_vector_t<std::list<TransitionEnd>> edge_transition_ends; // We only map the half edge in the upward direction. mapped items are not sorted
|
||
|
generateAllTransitionEnds(edge_transition_ends);
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-generateTransitioningRibs-ends-%d.svg", iRun++), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
applyTransitions(edge_transition_ends);
|
||
|
// Note that the shared pointer lists will be out of scope and thus destroyed here, since the remaining refs are weak_ptr.
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
++iRun;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
void SkeletalTrapezoidation::generateTransitionMids(ptr_vector_t<std::list<TransitionMiddle>>& edge_transitions)
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
assert(edge.data.centralIsSet());
|
||
|
if (!edge.data.isCentral())
|
||
|
{ // Only central regions introduce transitions
|
||
|
continue;
|
||
|
}
|
||
|
coord_t start_R = edge.from->data.distance_to_boundary;
|
||
|
coord_t end_R = edge.to->data.distance_to_boundary;
|
||
|
int start_bead_count = edge.from->data.bead_count;
|
||
|
int end_bead_count = edge.to->data.bead_count;
|
||
|
|
||
|
if (start_R == end_R)
|
||
|
{ // No transitions occur when both end points have the same distance_to_boundary
|
||
|
assert(edge.from->data.bead_count == edge.to->data.bead_count);
|
||
|
if(edge.from->data.bead_count != edge.to->data.bead_count)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Bead count " << edge.from->data.bead_count << " is different from " << edge.to->data.bead_count << " even though distance to boundary is the same.";
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
else if (start_R > end_R)
|
||
|
{ // Only consider those half-edges which are going from a lower to a higher distance_to_boundary
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (edge.from->data.bead_count == edge.to->data.bead_count)
|
||
|
{ // No transitions should occur according to the enforced bead counts
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (start_bead_count > beading_strategy.getOptimalBeadCount(start_R * 2)
|
||
|
|| end_bead_count > beading_strategy.getOptimalBeadCount(end_R * 2))
|
||
|
{ // Wasn't the case earlier in this function because of already introduced transitions
|
||
|
BOOST_LOG_TRIVIAL(error) << "transitioning segment overlap! (?)";
|
||
|
}
|
||
|
assert(start_R < end_R);
|
||
|
if(start_R >= end_R)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Transitioning the wrong way around! This function expects to transition from small R to big R, but was transitioning from " << start_R << " to " << end_R;
|
||
|
}
|
||
|
coord_t edge_size = (edge.from->p - edge.to->p).cast<int64_t>().norm();
|
||
|
for (int transition_lower_bead_count = start_bead_count; transition_lower_bead_count < end_bead_count; transition_lower_bead_count++)
|
||
|
{
|
||
|
coord_t mid_R = beading_strategy.getTransitionThickness(transition_lower_bead_count) / 2;
|
||
|
if (mid_R > end_R)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(error) << "transition on segment lies outside of segment!";
|
||
|
mid_R = end_R;
|
||
|
}
|
||
|
if (mid_R < start_R)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(error) << "transition on segment lies outside of segment!";
|
||
|
mid_R = start_R;
|
||
|
}
|
||
|
coord_t mid_pos = int64_t(edge_size) * int64_t(mid_R - start_R) / int64_t(end_R - start_R);
|
||
|
|
||
|
assert(mid_pos >= 0);
|
||
|
assert(mid_pos <= edge_size);
|
||
|
if(mid_pos < 0 || mid_pos > edge_size)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Transition mid is out of bounds of the edge.";
|
||
|
}
|
||
|
auto transitions = edge.data.getTransitions();
|
||
|
constexpr bool ignore_empty = true;
|
||
|
assert((! edge.data.hasTransitions(ignore_empty)) || mid_pos >= transitions->back().pos);
|
||
|
if (! edge.data.hasTransitions(ignore_empty))
|
||
|
{
|
||
|
edge_transitions.emplace_back(std::make_shared<std::list<TransitionMiddle>>());
|
||
|
edge.data.setTransitions(edge_transitions.back()); // initialization
|
||
|
transitions = edge.data.getTransitions();
|
||
|
}
|
||
|
transitions->emplace_back(mid_pos, transition_lower_bead_count, mid_R);
|
||
|
}
|
||
|
assert((edge.from->data.bead_count == edge.to->data.bead_count) || edge.data.hasTransitions());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::filterTransitionMids()
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (! edge.data.hasTransitions())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
auto& transitions = *edge.data.getTransitions();
|
||
|
|
||
|
// This is how stuff should be stored in transitions
|
||
|
assert(transitions.front().lower_bead_count <= transitions.back().lower_bead_count);
|
||
|
assert(edge.from->data.distance_to_boundary <= edge.to->data.distance_to_boundary);
|
||
|
|
||
|
const Point a = edge.from->p;
|
||
|
const Point b = edge.to->p;
|
||
|
Point ab = b - a;
|
||
|
coord_t ab_size = ab.cast<int64_t>().norm();
|
||
|
|
||
|
bool going_up = true;
|
||
|
std::list<TransitionMidRef> to_be_dissolved_back = dissolveNearbyTransitions(&edge, transitions.back(), ab_size - transitions.back().pos, transition_filter_dist, going_up);
|
||
|
bool should_dissolve_back = !to_be_dissolved_back.empty();
|
||
|
for (TransitionMidRef& ref : to_be_dissolved_back)
|
||
|
{
|
||
|
dissolveBeadCountRegion(&edge, transitions.back().lower_bead_count + 1, transitions.back().lower_bead_count);
|
||
|
ref.edge->data.getTransitions()->erase(ref.transition_it);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
coord_t trans_bead_count = transitions.back().lower_bead_count;
|
||
|
coord_t upper_transition_half_length = (1.0 - beading_strategy.getTransitionAnchorPos(trans_bead_count)) * beading_strategy.getTransitioningLength(trans_bead_count);
|
||
|
should_dissolve_back |= filterEndOfCentralTransition(&edge, ab_size - transitions.back().pos, upper_transition_half_length, trans_bead_count);
|
||
|
}
|
||
|
|
||
|
if (should_dissolve_back)
|
||
|
{
|
||
|
transitions.pop_back();
|
||
|
}
|
||
|
if (transitions.empty())
|
||
|
{ // FilterEndOfCentralTransition gives inconsistent new bead count when executing for the same transition in two directions.
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
going_up = false;
|
||
|
std::list<TransitionMidRef> to_be_dissolved_front = dissolveNearbyTransitions(edge.twin, transitions.front(), transitions.front().pos, transition_filter_dist, going_up);
|
||
|
bool should_dissolve_front = !to_be_dissolved_front.empty();
|
||
|
for (TransitionMidRef& ref : to_be_dissolved_front)
|
||
|
{
|
||
|
dissolveBeadCountRegion(edge.twin, transitions.front().lower_bead_count, transitions.front().lower_bead_count + 1);
|
||
|
ref.edge->data.getTransitions()->erase(ref.transition_it);
|
||
|
}
|
||
|
|
||
|
{
|
||
|
coord_t trans_bead_count = transitions.front().lower_bead_count;
|
||
|
coord_t lower_transition_half_length = beading_strategy.getTransitionAnchorPos(trans_bead_count) * beading_strategy.getTransitioningLength(trans_bead_count);
|
||
|
should_dissolve_front |= filterEndOfCentralTransition(edge.twin, transitions.front().pos, lower_transition_half_length, trans_bead_count + 1);
|
||
|
}
|
||
|
|
||
|
if (should_dissolve_front)
|
||
|
{
|
||
|
transitions.pop_front();
|
||
|
}
|
||
|
if (transitions.empty())
|
||
|
{ // FilterEndOfCentralTransition gives inconsistent new bead count when executing for the same transition in two directions.
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::list<SkeletalTrapezoidation::TransitionMidRef> SkeletalTrapezoidation::dissolveNearbyTransitions(edge_t* edge_to_start, TransitionMiddle& origin_transition, coord_t traveled_dist, coord_t max_dist, bool going_up)
|
||
|
{
|
||
|
std::list<TransitionMidRef> to_be_dissolved;
|
||
|
if (traveled_dist > max_dist)
|
||
|
return to_be_dissolved;
|
||
|
|
||
|
bool should_dissolve = true;
|
||
|
for (edge_t* edge = edge_to_start->next; edge && edge != edge_to_start->twin; edge = edge->twin->next){
|
||
|
if (!edge->data.isCentral())
|
||
|
continue;
|
||
|
|
||
|
Point a = edge->from->p;
|
||
|
Point b = edge->to->p;
|
||
|
Point ab = b - a;
|
||
|
coord_t ab_size = ab.cast<int64_t>().norm();
|
||
|
bool is_aligned = edge->isUpward();
|
||
|
edge_t* aligned_edge = is_aligned? edge : edge->twin;
|
||
|
bool seen_transition_on_this_edge = false;
|
||
|
|
||
|
const coord_t origin_radius = origin_transition.feature_radius;
|
||
|
const coord_t radius_here = edge->from->data.distance_to_boundary;
|
||
|
const bool dissolve_result_is_odd = bool(origin_transition.lower_bead_count % 2) == going_up;
|
||
|
const coord_t width_deviation = std::abs(origin_radius - radius_here) * 2; // times by two because the deviation happens at both sides of the significant edge
|
||
|
const coord_t line_width_deviation = dissolve_result_is_odd ? width_deviation : width_deviation / 2; // assume the deviation will be split over either 1 or 2 lines, i.e. assume wall_distribution_count = 1
|
||
|
if (line_width_deviation > allowed_filter_deviation)
|
||
|
should_dissolve = false;
|
||
|
|
||
|
if (should_dissolve && aligned_edge->data.hasTransitions()) {
|
||
|
auto& transitions = *aligned_edge->data.getTransitions();
|
||
|
for (auto transition_it = transitions.begin(); transition_it != transitions.end(); ++ transition_it) { // Note: this is not necessarily iterating in the traveling direction!
|
||
|
// Check whether we should dissolve
|
||
|
coord_t pos = is_aligned? transition_it->pos : ab_size - transition_it->pos;
|
||
|
if (traveled_dist + pos < max_dist && transition_it->lower_bead_count == origin_transition.lower_bead_count) { // Only dissolve local optima
|
||
|
if (traveled_dist + pos < beading_strategy.getTransitioningLength(transition_it->lower_bead_count)) {
|
||
|
// Consecutive transitions both in/decreasing in bead count should never be closer together than the transition distance
|
||
|
assert(going_up != is_aligned || transition_it->lower_bead_count == 0);
|
||
|
}
|
||
|
to_be_dissolved.emplace_back(aligned_edge, transition_it);
|
||
|
seen_transition_on_this_edge = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (should_dissolve && !seen_transition_on_this_edge) {
|
||
|
std::list<SkeletalTrapezoidation::TransitionMidRef> to_be_dissolved_here = dissolveNearbyTransitions(edge, origin_transition, traveled_dist + ab_size, max_dist, going_up);
|
||
|
if (to_be_dissolved_here.empty()) { // The region is too long to be dissolved in this direction, so it cannot be dissolved in any direction.
|
||
|
to_be_dissolved.clear();
|
||
|
return to_be_dissolved;
|
||
|
}
|
||
|
to_be_dissolved.splice(to_be_dissolved.end(), to_be_dissolved_here); // Transfer to_be_dissolved_here into to_be_dissolved
|
||
|
should_dissolve = should_dissolve && !to_be_dissolved.empty();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!should_dissolve)
|
||
|
to_be_dissolved.clear();
|
||
|
|
||
|
return to_be_dissolved;
|
||
|
}
|
||
|
|
||
|
|
||
|
void SkeletalTrapezoidation::dissolveBeadCountRegion(edge_t* edge_to_start, coord_t from_bead_count, coord_t to_bead_count)
|
||
|
{
|
||
|
assert(from_bead_count != to_bead_count);
|
||
|
if (edge_to_start->to->data.bead_count != from_bead_count)
|
||
|
return;
|
||
|
|
||
|
edge_to_start->to->data.bead_count = to_bead_count;
|
||
|
for (edge_t* edge = edge_to_start->next; edge && edge != edge_to_start->twin; edge = edge->twin->next)
|
||
|
{
|
||
|
if (!edge->data.isCentral())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
dissolveBeadCountRegion(edge, from_bead_count, to_bead_count);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool SkeletalTrapezoidation::filterEndOfCentralTransition(edge_t* edge_to_start, coord_t traveled_dist, coord_t max_dist, coord_t replacing_bead_count)
|
||
|
{
|
||
|
if (traveled_dist > max_dist)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool is_end_of_central = true;
|
||
|
bool should_dissolve = false;
|
||
|
for (edge_t* next_edge = edge_to_start->next; next_edge && next_edge != edge_to_start->twin; next_edge = next_edge->twin->next)
|
||
|
{
|
||
|
if (next_edge->data.isCentral())
|
||
|
{
|
||
|
coord_t length = (next_edge->to->p - next_edge->from->p).cast<int64_t>().norm();
|
||
|
should_dissolve |= filterEndOfCentralTransition(next_edge, traveled_dist + length, max_dist, replacing_bead_count);
|
||
|
is_end_of_central = false;
|
||
|
}
|
||
|
}
|
||
|
if (is_end_of_central && traveled_dist < max_dist)
|
||
|
{
|
||
|
should_dissolve = true;
|
||
|
}
|
||
|
|
||
|
if (should_dissolve)
|
||
|
{
|
||
|
edge_to_start->to->data.bead_count = replacing_bead_count;
|
||
|
}
|
||
|
return should_dissolve;
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::generateAllTransitionEnds(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends)
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (! edge.data.hasTransitions())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
auto& transition_positions = *edge.data.getTransitions();
|
||
|
|
||
|
assert(edge.from->data.distance_to_boundary <= edge.to->data.distance_to_boundary);
|
||
|
for (TransitionMiddle& transition_middle : transition_positions)
|
||
|
{
|
||
|
assert(transition_positions.front().pos <= transition_middle.pos);
|
||
|
assert(transition_middle.pos <= transition_positions.back().pos);
|
||
|
generateTransitionEnds(edge, transition_middle.pos, transition_middle.lower_bead_count, edge_transition_ends);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::generateTransitionEnds(edge_t& edge, coord_t mid_pos, coord_t lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends)
|
||
|
{
|
||
|
const Point a = edge.from->p;
|
||
|
const Point b = edge.to->p;
|
||
|
const Point ab = b - a;
|
||
|
const coord_t ab_size = ab.cast<int64_t>().norm();
|
||
|
|
||
|
const coord_t transition_length = beading_strategy.getTransitioningLength(lower_bead_count);
|
||
|
const float transition_mid_position = beading_strategy.getTransitionAnchorPos(lower_bead_count);
|
||
|
constexpr float inner_bead_width_ratio_after_transition = 1.0;
|
||
|
|
||
|
constexpr coord_t start_rest = 0;
|
||
|
const float mid_rest = transition_mid_position * inner_bead_width_ratio_after_transition;
|
||
|
constexpr float end_rest = inner_bead_width_ratio_after_transition;
|
||
|
|
||
|
{ // Lower bead count transition end
|
||
|
const coord_t start_pos = ab_size - mid_pos;
|
||
|
const coord_t transition_half_length = transition_mid_position * int64_t(transition_length);
|
||
|
const coord_t end_pos = start_pos + transition_half_length;
|
||
|
generateTransitionEnd(*edge.twin, start_pos, end_pos, transition_half_length, mid_rest, start_rest, lower_bead_count, edge_transition_ends);
|
||
|
}
|
||
|
|
||
|
{ // Upper bead count transition end
|
||
|
const coord_t start_pos = mid_pos;
|
||
|
const coord_t transition_half_length = (1.0 - transition_mid_position) * transition_length;
|
||
|
const coord_t end_pos = mid_pos + transition_half_length;
|
||
|
#ifdef DEBUG
|
||
|
if (! generateTransitionEnd(edge, start_pos, end_pos, transition_half_length, mid_rest, end_rest, lower_bead_count, edge_transition_ends))
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "There must have been at least one direction in which the bead count is increasing enough for the transition to happen!";
|
||
|
}
|
||
|
#else
|
||
|
generateTransitionEnd(edge, start_pos, end_pos, transition_half_length, mid_rest, end_rest, lower_bead_count, edge_transition_ends);
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool SkeletalTrapezoidation::generateTransitionEnd(edge_t& edge, coord_t start_pos, coord_t end_pos, coord_t transition_half_length, double start_rest, double end_rest, coord_t lower_bead_count, ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends)
|
||
|
{
|
||
|
Point a = edge.from->p;
|
||
|
Point b = edge.to->p;
|
||
|
Point ab = b - a;
|
||
|
coord_t ab_size = ab.cast<int64_t>().norm(); // TODO: prevent recalculation of these values
|
||
|
|
||
|
assert(start_pos <= ab_size);
|
||
|
if(start_pos > ab_size)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Start position of edge is beyond edge range.";
|
||
|
}
|
||
|
|
||
|
bool going_up = end_rest > start_rest;
|
||
|
|
||
|
assert(edge.data.isCentral());
|
||
|
if (!edge.data.isCentral())
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "This function shouldn't generate ends in or beyond non-central regions.";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (end_pos > ab_size)
|
||
|
{ // Recurse on all further edges
|
||
|
float rest = end_rest - (start_rest - end_rest) * (end_pos - ab_size) / (start_pos - end_pos);
|
||
|
assert(rest >= 0);
|
||
|
assert(rest <= std::max(end_rest, start_rest));
|
||
|
assert(rest >= std::min(end_rest, start_rest));
|
||
|
|
||
|
coord_t central_edge_count = 0;
|
||
|
for (edge_t* outgoing = edge.next; outgoing && outgoing != edge.twin; outgoing = outgoing->twin->next)
|
||
|
{
|
||
|
if (!outgoing->data.isCentral()) continue;
|
||
|
central_edge_count++;
|
||
|
}
|
||
|
|
||
|
bool is_only_going_down = true;
|
||
|
bool has_recursed = false;
|
||
|
for (edge_t* outgoing = edge.next; outgoing && outgoing != edge.twin;)
|
||
|
{
|
||
|
edge_t* next = outgoing->twin->next; // Before we change the outgoing edge itself
|
||
|
if (!outgoing->data.isCentral())
|
||
|
{
|
||
|
outgoing = next;
|
||
|
continue; // Don't put transition ends in non-central regions
|
||
|
}
|
||
|
if (central_edge_count > 1 && going_up && isGoingDown(outgoing, 0, end_pos - ab_size + transition_half_length, lower_bead_count))
|
||
|
{ // We're after a 3-way_all-central_junction-node and going in the direction of lower bead count
|
||
|
// don't introduce a transition end along this central direction, because this direction is the downward direction
|
||
|
// while we are supposed to be [going_up]
|
||
|
outgoing = next;
|
||
|
continue;
|
||
|
}
|
||
|
bool is_going_down = generateTransitionEnd(*outgoing, 0, end_pos - ab_size, transition_half_length, rest, end_rest, lower_bead_count, edge_transition_ends);
|
||
|
is_only_going_down &= is_going_down;
|
||
|
outgoing = next;
|
||
|
has_recursed = true;
|
||
|
}
|
||
|
if (!going_up || (has_recursed && !is_only_going_down))
|
||
|
{
|
||
|
edge.to->data.transition_ratio = rest;
|
||
|
edge.to->data.bead_count = lower_bead_count;
|
||
|
}
|
||
|
return is_only_going_down;
|
||
|
}
|
||
|
else // end_pos < ab_size
|
||
|
{ // Add transition end point here
|
||
|
bool is_lower_end = end_rest == 0; // TODO collapse this parameter into the bool for which it is used here!
|
||
|
coord_t pos = -1;
|
||
|
|
||
|
edge_t* upward_edge = nullptr;
|
||
|
if (edge.isUpward())
|
||
|
{
|
||
|
upward_edge = &edge;
|
||
|
pos = end_pos;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
upward_edge = edge.twin;
|
||
|
pos = ab_size - end_pos;
|
||
|
}
|
||
|
|
||
|
if(!upward_edge->data.hasTransitionEnds())
|
||
|
{
|
||
|
//This edge doesn't have a data structure yet for the transition ends. Make one.
|
||
|
edge_transition_ends.emplace_back(std::make_shared<std::list<TransitionEnd>>());
|
||
|
upward_edge->data.setTransitionEnds(edge_transition_ends.back());
|
||
|
}
|
||
|
auto transitions = upward_edge->data.getTransitionEnds();
|
||
|
|
||
|
//Add a transition to it (on the correct side).
|
||
|
assert(ab_size == (edge.twin->from->p - edge.twin->to->p).cast<int64_t>().norm());
|
||
|
assert(pos <= ab_size);
|
||
|
if (transitions->empty() || pos < transitions->front().pos)
|
||
|
{ // Preorder so that sorting later on is faster
|
||
|
transitions->emplace_front(pos, lower_bead_count, is_lower_end);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
transitions->emplace_back(pos, lower_bead_count, is_lower_end);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
bool SkeletalTrapezoidation::isGoingDown(edge_t* outgoing, coord_t traveled_dist, coord_t max_dist, coord_t lower_bead_count) const
|
||
|
{
|
||
|
// NOTE: the logic below is not fully thought through.
|
||
|
// TODO: take transition mids into account
|
||
|
if (outgoing->to->data.distance_to_boundary == 0)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
bool is_upward = outgoing->to->data.distance_to_boundary >= outgoing->from->data.distance_to_boundary;
|
||
|
edge_t* upward_edge = is_upward? outgoing : outgoing->twin;
|
||
|
if (outgoing->to->data.bead_count > lower_bead_count + 1)
|
||
|
{
|
||
|
assert(upward_edge->data.hasTransitions() && "If the bead count is going down there has to be a transition mid!");
|
||
|
if(!upward_edge->data.hasTransitions())
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "If the bead count is going down there has to be a transition mid!";
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
coord_t length = (outgoing->to->p - outgoing->from->p).cast<int64_t>().norm();
|
||
|
if (upward_edge->data.hasTransitions())
|
||
|
{
|
||
|
auto& transition_mids = *upward_edge->data.getTransitions();
|
||
|
TransitionMiddle& mid = is_upward? transition_mids.front() : transition_mids.back();
|
||
|
if (
|
||
|
mid.lower_bead_count == lower_bead_count &&
|
||
|
((is_upward && mid.pos + traveled_dist < max_dist)
|
||
|
|| (!is_upward && length - mid.pos + traveled_dist < max_dist))
|
||
|
)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
if (traveled_dist + length > max_dist)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (outgoing->to->data.bead_count <= lower_bead_count
|
||
|
&& !(outgoing->to->data.bead_count == lower_bead_count && outgoing->to->data.transition_ratio > 0.0))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool is_only_going_down = true;
|
||
|
bool has_recursed = false;
|
||
|
for (edge_t* next = outgoing->next; next && next != outgoing->twin; next = next->twin->next)
|
||
|
{
|
||
|
if (!next->data.isCentral())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
bool is_going_down = isGoingDown(next, traveled_dist + length, max_dist, lower_bead_count);
|
||
|
is_only_going_down &= is_going_down;
|
||
|
has_recursed = true;
|
||
|
}
|
||
|
return has_recursed && is_only_going_down;
|
||
|
}
|
||
|
|
||
|
static inline Point normal(const Point& p0, coord_t len)
|
||
|
{
|
||
|
int64_t _len = p0.cast<int64_t>().norm();
|
||
|
if (_len < 1)
|
||
|
return Point(len, 0);
|
||
|
return (p0.cast<int64_t>() * int64_t(len) / _len).cast<coord_t>();
|
||
|
};
|
||
|
|
||
|
void SkeletalTrapezoidation::applyTransitions(ptr_vector_t<std::list<TransitionEnd>>& edge_transition_ends)
|
||
|
{
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (edge.twin->data.hasTransitionEnds())
|
||
|
{
|
||
|
coord_t length = (edge.from->p - edge.to->p).cast<int64_t>().norm();
|
||
|
auto& twin_transition_ends = *edge.twin->data.getTransitionEnds();
|
||
|
if (! edge.data.hasTransitionEnds())
|
||
|
{
|
||
|
edge_transition_ends.emplace_back(std::make_shared<std::list<TransitionEnd>>());
|
||
|
edge.data.setTransitionEnds(edge_transition_ends.back());
|
||
|
}
|
||
|
auto& transition_ends = *edge.data.getTransitionEnds();
|
||
|
for (TransitionEnd& end : twin_transition_ends)
|
||
|
{
|
||
|
transition_ends.emplace_back(length - end.pos, end.lower_bead_count, end.is_lower_end);
|
||
|
}
|
||
|
twin_transition_ends.clear();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (! edge.data.hasTransitionEnds())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
assert(edge.data.isCentral());
|
||
|
|
||
|
auto& transitions = *edge.data.getTransitionEnds();
|
||
|
transitions.sort([](const TransitionEnd& a, const TransitionEnd& b) { return a.pos < b.pos; } );
|
||
|
|
||
|
node_t* from = edge.from;
|
||
|
node_t* to = edge.to;
|
||
|
Point a = from->p;
|
||
|
Point b = to->p;
|
||
|
Point ab = b - a;
|
||
|
coord_t ab_size = (ab).cast<int64_t>().norm();
|
||
|
|
||
|
edge_t* last_edge_replacing_input = &edge;
|
||
|
for (TransitionEnd& transition_end : transitions)
|
||
|
{
|
||
|
coord_t new_node_bead_count = transition_end.is_lower_end? transition_end.lower_bead_count : transition_end.lower_bead_count + 1;
|
||
|
coord_t end_pos = transition_end.pos;
|
||
|
node_t* close_node = (end_pos < ab_size / 2)? from : to;
|
||
|
if ((end_pos < snap_dist || end_pos > ab_size - snap_dist)
|
||
|
&& close_node->data.bead_count == new_node_bead_count
|
||
|
)
|
||
|
{
|
||
|
assert(end_pos <= ab_size);
|
||
|
close_node->data.transition_ratio = 0;
|
||
|
continue;
|
||
|
}
|
||
|
Point mid = a + normal(ab, end_pos);
|
||
|
|
||
|
assert(last_edge_replacing_input->data.isCentral());
|
||
|
assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD);
|
||
|
last_edge_replacing_input = graph.insertNode(last_edge_replacing_input, mid, new_node_bead_count);
|
||
|
assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD);
|
||
|
assert(last_edge_replacing_input->data.isCentral());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool SkeletalTrapezoidation::isEndOfCentral(const edge_t& edge_to) const
|
||
|
{
|
||
|
if (!edge_to.data.isCentral())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (!edge_to.next)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
for (const edge_t* edge = edge_to.next; edge && edge != edge_to.twin; edge = edge->twin->next)
|
||
|
{
|
||
|
if (edge->data.isCentral())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
assert(edge->twin);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::generateExtraRibs()
|
||
|
{
|
||
|
for (auto edge_it = graph.edges.begin(); edge_it != graph.edges.end(); ++edge_it)
|
||
|
{
|
||
|
edge_t& edge = *edge_it;
|
||
|
|
||
|
if (!edge.data.isCentral()
|
||
|
|| shorter_then(edge.to->p - edge.from->p, discretization_step_size)
|
||
|
|| edge.from->data.distance_to_boundary >= edge.to->data.distance_to_boundary)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
|
||
|
std::vector<coord_t> rib_thicknesses = beading_strategy.getNonlinearThicknesses(edge.from->data.bead_count);
|
||
|
|
||
|
if (rib_thicknesses.empty()) continue;
|
||
|
|
||
|
// Preload some variables before [edge] gets changed
|
||
|
node_t* from = edge.from;
|
||
|
node_t* to = edge.to;
|
||
|
Point a = from->p;
|
||
|
Point b = to->p;
|
||
|
Point ab = b - a;
|
||
|
coord_t ab_size = ab.cast<int64_t>().norm();
|
||
|
coord_t a_R = edge.from->data.distance_to_boundary;
|
||
|
coord_t b_R = edge.to->data.distance_to_boundary;
|
||
|
|
||
|
edge_t* last_edge_replacing_input = &edge;
|
||
|
for (coord_t rib_thickness : rib_thicknesses)
|
||
|
{
|
||
|
if (rib_thickness / 2 <= a_R)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
if (rib_thickness / 2 >= b_R)
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
coord_t new_node_bead_count = std::min(edge.from->data.bead_count, edge.to->data.bead_count);
|
||
|
coord_t end_pos = int64_t(ab_size) * int64_t(rib_thickness / 2 - a_R) / int64_t(b_R - a_R);
|
||
|
assert(end_pos > 0);
|
||
|
assert(end_pos < ab_size);
|
||
|
node_t* close_node = (end_pos < ab_size / 2)? from : to;
|
||
|
if ((end_pos < snap_dist || end_pos > ab_size - snap_dist)
|
||
|
&& close_node->data.bead_count == new_node_bead_count
|
||
|
)
|
||
|
{
|
||
|
assert(end_pos <= ab_size);
|
||
|
close_node->data.transition_ratio = 0;
|
||
|
continue;
|
||
|
}
|
||
|
Point mid = a + normal(ab, end_pos);
|
||
|
|
||
|
assert(last_edge_replacing_input->data.isCentral());
|
||
|
assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD);
|
||
|
last_edge_replacing_input = graph.insertNode(last_edge_replacing_input, mid, new_node_bead_count);
|
||
|
assert(last_edge_replacing_input->data.type != SkeletalTrapezoidationEdge::EdgeType::EXTRA_VD);
|
||
|
assert(last_edge_replacing_input->data.isCentral());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ^^^^^^^^^^^^^^^^^^^^^
|
||
|
// TRANSTISIONING
|
||
|
// =====================
|
||
|
// TOOLPATH GENERATION
|
||
|
// vvvvvvvvvvvvvvvvvvvvv
|
||
|
//
|
||
|
|
||
|
void SkeletalTrapezoidation::generateSegments()
|
||
|
{
|
||
|
std::vector<edge_t*> upward_quad_mids;
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (edge.prev && edge.next && edge.isUpward())
|
||
|
{
|
||
|
upward_quad_mids.emplace_back(&edge);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::sort(upward_quad_mids.begin(), upward_quad_mids.end(), [](edge_t* a, edge_t* b)
|
||
|
{
|
||
|
if (a->to->data.distance_to_boundary == b->to->data.distance_to_boundary)
|
||
|
{ // Ordering between two 'upward' edges of the same distance is important when one of the edges is flat and connected to the other
|
||
|
if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary
|
||
|
&& b->from->data.distance_to_boundary == b->to->data.distance_to_boundary)
|
||
|
{
|
||
|
coord_t max = std::numeric_limits<coord_t>::max();
|
||
|
coord_t a_dist_from_up = std::min(a->distToGoUp().value_or(max), a->twin->distToGoUp().value_or(max)) - (a->to->p - a->from->p).cast<int64_t>().norm();
|
||
|
coord_t b_dist_from_up = std::min(b->distToGoUp().value_or(max), b->twin->distToGoUp().value_or(max)) - (b->to->p - b->from->p).cast<int64_t>().norm();
|
||
|
return a_dist_from_up < b_dist_from_up;
|
||
|
}
|
||
|
else if (a->from->data.distance_to_boundary == a->to->data.distance_to_boundary)
|
||
|
{
|
||
|
return true; // Edge a might be 'above' edge b
|
||
|
}
|
||
|
else if (b->from->data.distance_to_boundary == b->to->data.distance_to_boundary)
|
||
|
{
|
||
|
return false; // Edge b might be 'above' edge a
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Ordering is not important
|
||
|
}
|
||
|
}
|
||
|
return a->to->data.distance_to_boundary > b->to->data.distance_to_boundary;
|
||
|
});
|
||
|
|
||
|
ptr_vector_t<BeadingPropagation> node_beadings;
|
||
|
{ // Store beading
|
||
|
for (node_t& node : graph.nodes)
|
||
|
{
|
||
|
if (node.data.bead_count <= 0)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
if (node.data.transition_ratio == 0)
|
||
|
{
|
||
|
node_beadings.emplace_back(new BeadingPropagation(beading_strategy.compute(node.data.distance_to_boundary * 2, node.data.bead_count)));
|
||
|
node.data.setBeading(node_beadings.back());
|
||
|
assert(node_beadings.back()->beading.total_thickness == node.data.distance_to_boundary * 2);
|
||
|
if(node_beadings.back()->beading.total_thickness != node.data.distance_to_boundary * 2)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "If transitioning to an endpoint (ratio 0), the node should be exactly in the middle.";
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Beading low_count_beading = beading_strategy.compute(node.data.distance_to_boundary * 2, node.data.bead_count);
|
||
|
Beading high_count_beading = beading_strategy.compute(node.data.distance_to_boundary * 2, node.data.bead_count + 1);
|
||
|
Beading merged = interpolate(low_count_beading, 1.0 - node.data.transition_ratio, high_count_beading);
|
||
|
node_beadings.emplace_back(new BeadingPropagation(merged));
|
||
|
node.data.setBeading(node_beadings.back());
|
||
|
assert(merged.total_thickness == node.data.distance_to_boundary * 2);
|
||
|
if(merged.total_thickness != node.data.distance_to_boundary * 2)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "If merging two beads, the new bead must be exactly in the middle.";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
static int iRun = 0;
|
||
|
export_graph_to_svg(debug_out_path("ST-generateSegments-before-propagation-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
propagateBeadingsUpward(upward_quad_mids, node_beadings);
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-generateSegments-upward-propagation-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
propagateBeadingsDownward(upward_quad_mids, node_beadings);
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-generateSegments-downward-propagation-%d.svg", iRun), this->graph, this->outline);
|
||
|
#endif
|
||
|
|
||
|
ptr_vector_t<LineJunctions> edge_junctions; // junctions ordered high R to low R
|
||
|
generateJunctions(node_beadings, edge_junctions);
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
export_graph_to_svg(debug_out_path("ST-generateSegments-junctions-%d.svg", iRun), this->graph, this->outline, edge_junctions);
|
||
|
#endif
|
||
|
|
||
|
connectJunctions(edge_junctions);
|
||
|
|
||
|
generateLocalMaximaSingleBeads();
|
||
|
|
||
|
#ifdef ARACHNE_DEBUG
|
||
|
++iRun;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
SkeletalTrapezoidation::edge_t* SkeletalTrapezoidation::getQuadMaxRedgeTo(edge_t* quad_start_edge)
|
||
|
{
|
||
|
assert(quad_start_edge->prev == nullptr);
|
||
|
assert(quad_start_edge->from->data.distance_to_boundary == 0);
|
||
|
coord_t max_R = -1;
|
||
|
edge_t* ret = nullptr;
|
||
|
for (edge_t* edge = quad_start_edge; edge; edge = edge->next)
|
||
|
{
|
||
|
coord_t r = edge->to->data.distance_to_boundary;
|
||
|
if (r > max_R)
|
||
|
{
|
||
|
max_R = r;
|
||
|
ret = edge;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!ret->next && ret->to->data.distance_to_boundary - scaled<coord_t>(0.005) < ret->from->data.distance_to_boundary)
|
||
|
{
|
||
|
ret = ret->prev;
|
||
|
}
|
||
|
assert(ret);
|
||
|
assert(ret->next);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::propagateBeadingsUpward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings)
|
||
|
{
|
||
|
for (auto upward_quad_mids_it = upward_quad_mids.rbegin(); upward_quad_mids_it != upward_quad_mids.rend(); ++upward_quad_mids_it)
|
||
|
{
|
||
|
edge_t* upward_edge = *upward_quad_mids_it;
|
||
|
if (upward_edge->to->data.bead_count >= 0)
|
||
|
{ // Don't override local beading
|
||
|
continue;
|
||
|
}
|
||
|
if (! upward_edge->from->data.hasBeading())
|
||
|
{ // Only propagate if we have something to propagate
|
||
|
continue;
|
||
|
}
|
||
|
BeadingPropagation& lower_beading = *upward_edge->from->data.getBeading();
|
||
|
if (upward_edge->to->data.hasBeading())
|
||
|
{ // Only propagate to places where there is place
|
||
|
continue;
|
||
|
}
|
||
|
assert((upward_edge->from->data.distance_to_boundary != upward_edge->to->data.distance_to_boundary || shorter_then(upward_edge->to->p - upward_edge->from->p, central_filter_dist)) && "zero difference R edges should always be central");
|
||
|
coord_t length = (upward_edge->to->p - upward_edge->from->p).cast<int64_t>().norm();
|
||
|
BeadingPropagation upper_beading = lower_beading;
|
||
|
upper_beading.dist_to_bottom_source += length;
|
||
|
upper_beading.is_upward_propagated_only = true;
|
||
|
node_beadings.emplace_back(new BeadingPropagation(upper_beading));
|
||
|
upward_edge->to->data.setBeading(node_beadings.back());
|
||
|
assert(upper_beading.beading.total_thickness <= upward_edge->to->data.distance_to_boundary * 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::propagateBeadingsDownward(std::vector<edge_t*>& upward_quad_mids, ptr_vector_t<BeadingPropagation>& node_beadings)
|
||
|
{
|
||
|
for (edge_t* upward_quad_mid : upward_quad_mids)
|
||
|
{
|
||
|
// Transfer beading information to lower nodes
|
||
|
if (!upward_quad_mid->data.isCentral())
|
||
|
{
|
||
|
// for equidistant edge: propagate from known beading to node with unknown beading
|
||
|
if (upward_quad_mid->from->data.distance_to_boundary == upward_quad_mid->to->data.distance_to_boundary
|
||
|
&& upward_quad_mid->from->data.hasBeading()
|
||
|
&& ! upward_quad_mid->to->data.hasBeading()
|
||
|
)
|
||
|
{
|
||
|
propagateBeadingsDownward(upward_quad_mid->twin, node_beadings);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
propagateBeadingsDownward(upward_quad_mid, node_beadings);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::propagateBeadingsDownward(edge_t* edge_to_peak, ptr_vector_t<BeadingPropagation>& node_beadings)
|
||
|
{
|
||
|
coord_t length = (edge_to_peak->to->p - edge_to_peak->from->p).cast<int64_t>().norm();
|
||
|
BeadingPropagation& top_beading = *getOrCreateBeading(edge_to_peak->to, node_beadings);
|
||
|
assert(top_beading.beading.total_thickness >= edge_to_peak->to->data.distance_to_boundary * 2);
|
||
|
if(top_beading.beading.total_thickness < edge_to_peak->to->data.distance_to_boundary * 2)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Top bead is beyond the center of the total width.";
|
||
|
}
|
||
|
assert(!top_beading.is_upward_propagated_only);
|
||
|
|
||
|
if(!edge_to_peak->from->data.hasBeading())
|
||
|
{ // Set new beading if there is no beading associated with the node yet
|
||
|
BeadingPropagation propagated_beading = top_beading;
|
||
|
propagated_beading.dist_from_top_source += length;
|
||
|
node_beadings.emplace_back(new BeadingPropagation(propagated_beading));
|
||
|
edge_to_peak->from->data.setBeading(node_beadings.back());
|
||
|
assert(propagated_beading.beading.total_thickness >= edge_to_peak->from->data.distance_to_boundary * 2);
|
||
|
if(propagated_beading.beading.total_thickness < edge_to_peak->from->data.distance_to_boundary * 2)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Propagated bead is beyond the center of the total width.";
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
BeadingPropagation& bottom_beading = *edge_to_peak->from->data.getBeading();
|
||
|
coord_t total_dist = top_beading.dist_from_top_source + length + bottom_beading.dist_to_bottom_source;
|
||
|
double ratio_of_top = static_cast<float>(bottom_beading.dist_to_bottom_source) / std::min(total_dist, beading_propagation_transition_dist);
|
||
|
ratio_of_top = std::max(0.0, ratio_of_top);
|
||
|
if (ratio_of_top >= 1.0)
|
||
|
{
|
||
|
bottom_beading = top_beading;
|
||
|
bottom_beading.dist_from_top_source += length;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Beading merged_beading = interpolate(top_beading.beading, ratio_of_top, bottom_beading.beading, edge_to_peak->from->data.distance_to_boundary);
|
||
|
bottom_beading = BeadingPropagation(merged_beading);
|
||
|
bottom_beading.is_upward_propagated_only = false;
|
||
|
assert(merged_beading.total_thickness >= edge_to_peak->from->data.distance_to_boundary * 2);
|
||
|
if(merged_beading.total_thickness < edge_to_peak->from->data.distance_to_boundary * 2)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Merged bead is beyond the center of the total width.";
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right, coord_t switching_radius) const
|
||
|
{
|
||
|
assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0);
|
||
|
Beading ret = interpolate(left, ratio_left_to_whole, right);
|
||
|
|
||
|
// TODO: don't use toolpath locations past the middle!
|
||
|
// TODO: stretch bead widths and locations of the higher bead count beading to fit in the left over space
|
||
|
coord_t next_inset_idx;
|
||
|
for (next_inset_idx = left.toolpath_locations.size() - 1; next_inset_idx >= 0; next_inset_idx--)
|
||
|
{
|
||
|
if (switching_radius > left.toolpath_locations[next_inset_idx])
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (next_inset_idx < 0)
|
||
|
{ // There is no next inset, because there is only one
|
||
|
assert(left.toolpath_locations.empty() || left.toolpath_locations.front() >= switching_radius);
|
||
|
return ret;
|
||
|
}
|
||
|
if (next_inset_idx + 1 == coord_t(left.toolpath_locations.size()))
|
||
|
{ // We cant adjust to fit the next edge because there is no previous one?!
|
||
|
return ret;
|
||
|
}
|
||
|
assert(next_inset_idx < coord_t(left.toolpath_locations.size()));
|
||
|
assert(left.toolpath_locations[next_inset_idx] <= switching_radius);
|
||
|
assert(left.toolpath_locations[next_inset_idx + 1] >= switching_radius);
|
||
|
if (ret.toolpath_locations[next_inset_idx] > switching_radius)
|
||
|
{ // One inset disappeared between left and the merged one
|
||
|
// solve for ratio f:
|
||
|
// f*l + (1-f)*r = s
|
||
|
// f*l + r - f*r = s
|
||
|
// f*(l-r) + r = s
|
||
|
// f*(l-r) = s - r
|
||
|
// f = (s-r) / (l-r)
|
||
|
float new_ratio = static_cast<float>(switching_radius - right.toolpath_locations[next_inset_idx]) / static_cast<float>(left.toolpath_locations[next_inset_idx] - right.toolpath_locations[next_inset_idx]);
|
||
|
new_ratio = std::min(1.0, new_ratio + 0.1);
|
||
|
return interpolate(left, new_ratio, right);
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
SkeletalTrapezoidation::Beading SkeletalTrapezoidation::interpolate(const Beading& left, double ratio_left_to_whole, const Beading& right) const
|
||
|
{
|
||
|
assert(ratio_left_to_whole >= 0.0 && ratio_left_to_whole <= 1.0);
|
||
|
float ratio_right_to_whole = 1.0 - ratio_left_to_whole;
|
||
|
|
||
|
Beading ret = (left.total_thickness > right.total_thickness)? left : right;
|
||
|
for (size_t inset_idx = 0; inset_idx < std::min(left.bead_widths.size(), right.bead_widths.size()); inset_idx++)
|
||
|
{
|
||
|
if(left.bead_widths[inset_idx] == 0 || right.bead_widths[inset_idx] == 0)
|
||
|
{
|
||
|
ret.bead_widths[inset_idx] = 0; //0-width wall markers stay 0-width.
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ret.bead_widths[inset_idx] = ratio_left_to_whole * left.bead_widths[inset_idx] + ratio_right_to_whole * right.bead_widths[inset_idx];
|
||
|
}
|
||
|
ret.toolpath_locations[inset_idx] = ratio_left_to_whole * left.toolpath_locations[inset_idx] + ratio_right_to_whole * right.toolpath_locations[inset_idx];
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::generateJunctions(ptr_vector_t<BeadingPropagation>& node_beadings, ptr_vector_t<LineJunctions>& edge_junctions)
|
||
|
{
|
||
|
for (edge_t& edge_ : graph.edges)
|
||
|
{
|
||
|
edge_t* edge = &edge_;
|
||
|
if (edge->from->data.distance_to_boundary > edge->to->data.distance_to_boundary)
|
||
|
{ // Only consider the upward half-edges
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
coord_t start_R = edge->to->data.distance_to_boundary; // higher R
|
||
|
coord_t end_R = edge->from->data.distance_to_boundary; // lower R
|
||
|
|
||
|
if ((edge->from->data.bead_count == edge->to->data.bead_count && edge->from->data.bead_count >= 0)
|
||
|
|| end_R >= start_R)
|
||
|
{ // No beads to generate
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
Beading* beading = &getOrCreateBeading(edge->to, node_beadings)->beading;
|
||
|
edge_junctions.emplace_back(std::make_shared<LineJunctions>());
|
||
|
edge_.data.setExtrusionJunctions(edge_junctions.back()); // initialization
|
||
|
LineJunctions& ret = *edge_junctions.back();
|
||
|
|
||
|
assert(beading->total_thickness >= edge->to->data.distance_to_boundary * 2);
|
||
|
if(beading->total_thickness < edge->to->data.distance_to_boundary * 2)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Generated junction is beyond the center of total width.";
|
||
|
}
|
||
|
|
||
|
Point a = edge->to->p;
|
||
|
Point b = edge->from->p;
|
||
|
Point ab = b - a;
|
||
|
|
||
|
const size_t num_junctions = beading->toolpath_locations.size();
|
||
|
size_t junction_idx;
|
||
|
// Compute starting junction_idx for this segment
|
||
|
for (junction_idx = (std::max(size_t(1), beading->toolpath_locations.size()) - 1) / 2; junction_idx < num_junctions; junction_idx--)
|
||
|
{
|
||
|
coord_t bead_R = beading->toolpath_locations[junction_idx];
|
||
|
// toolpath_locations computed inside DistributedBeadingStrategy could be off by 1 because of rounding errors.
|
||
|
// In GH issue #8472, these roundings errors caused missing the middle extrusion.
|
||
|
// Adding small epsilon should help resolve those cases.
|
||
|
if (bead_R <= start_R + 1)
|
||
|
{ // Junction coinciding with start node is used in this function call
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Robustness against odd segments which might lie just slightly outside of the range due to rounding errors
|
||
|
// not sure if this is really needed (TODO)
|
||
|
if (junction_idx + 1 < num_junctions
|
||
|
&& beading->toolpath_locations[junction_idx + 1] <= start_R + scaled<coord_t>(0.005)
|
||
|
&& beading->total_thickness < start_R + scaled<coord_t>(0.005)
|
||
|
)
|
||
|
{
|
||
|
junction_idx++;
|
||
|
}
|
||
|
|
||
|
for (; junction_idx < num_junctions; junction_idx--) //When junction_idx underflows, it'll be more than num_junctions too.
|
||
|
{
|
||
|
coord_t bead_R = beading->toolpath_locations[junction_idx];
|
||
|
assert(bead_R >= 0);
|
||
|
if (bead_R < end_R)
|
||
|
{ // Junction coinciding with a node is handled by the next segment
|
||
|
break;
|
||
|
}
|
||
|
Point junction(a + (ab.cast<int64_t>() * int64_t(bead_R - start_R) / int64_t(end_R - start_R)).cast<coord_t>());
|
||
|
if (bead_R > start_R - scaled<coord_t>(0.005))
|
||
|
{ // Snap to start node if it is really close, in order to be able to see 3-way intersection later on more robustly
|
||
|
junction = a;
|
||
|
}
|
||
|
ret.emplace_back(junction, beading->bead_widths[junction_idx], junction_idx);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::shared_ptr<SkeletalTrapezoidationJoint::BeadingPropagation> SkeletalTrapezoidation::getOrCreateBeading(node_t* node, ptr_vector_t<BeadingPropagation>& node_beadings)
|
||
|
{
|
||
|
if (! node->data.hasBeading())
|
||
|
{
|
||
|
if (node->data.bead_count == -1)
|
||
|
{ // This bug is due to too small central edges
|
||
|
constexpr coord_t nearby_dist = scaled<coord_t>(0.1);
|
||
|
auto nearest_beading = getNearestBeading(node, nearby_dist);
|
||
|
if (nearest_beading)
|
||
|
{
|
||
|
return nearest_beading;
|
||
|
}
|
||
|
|
||
|
// Else make a new beading:
|
||
|
bool has_central_edge = false;
|
||
|
bool first = true;
|
||
|
coord_t dist = std::numeric_limits<coord_t>::max();
|
||
|
for (edge_t* edge = node->incident_edge; edge && (first || edge != node->incident_edge); edge = edge->twin->next)
|
||
|
{
|
||
|
if (edge->data.isCentral())
|
||
|
{
|
||
|
has_central_edge = true;
|
||
|
}
|
||
|
assert(edge->to->data.distance_to_boundary >= 0);
|
||
|
dist = std::min(dist, edge->to->data.distance_to_boundary + coord_t((edge->to->p - edge->from->p).cast<int64_t>().norm()));
|
||
|
first = false;
|
||
|
}
|
||
|
if (!has_central_edge)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(error) << "Unknown beading for non-central node!";
|
||
|
}
|
||
|
assert(dist != std::numeric_limits<coord_t>::max());
|
||
|
node->data.bead_count = beading_strategy.getOptimalBeadCount(dist * 2);
|
||
|
}
|
||
|
assert(node->data.bead_count != -1);
|
||
|
node_beadings.emplace_back(new BeadingPropagation(beading_strategy.compute(node->data.distance_to_boundary * 2, node->data.bead_count)));
|
||
|
node->data.setBeading(node_beadings.back());
|
||
|
}
|
||
|
assert(node->data.hasBeading());
|
||
|
return node->data.getBeading();
|
||
|
}
|
||
|
|
||
|
std::shared_ptr<SkeletalTrapezoidationJoint::BeadingPropagation> SkeletalTrapezoidation::getNearestBeading(node_t* node, coord_t max_dist)
|
||
|
{
|
||
|
struct DistEdge
|
||
|
{
|
||
|
edge_t* edge_to;
|
||
|
coord_t dist;
|
||
|
DistEdge(edge_t* edge_to, coord_t dist)
|
||
|
: edge_to(edge_to), dist(dist)
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
auto compare = [](const DistEdge& l, const DistEdge& r) -> bool { return l.dist > r.dist; };
|
||
|
std::priority_queue<DistEdge, std::vector<DistEdge>, decltype(compare)> further_edges(compare);
|
||
|
bool first = true;
|
||
|
for (edge_t* outgoing = node->incident_edge; outgoing && (first || outgoing != node->incident_edge); outgoing = outgoing->twin->next)
|
||
|
{
|
||
|
further_edges.emplace(outgoing, (outgoing->to->p - outgoing->from->p).cast<int64_t>().norm());
|
||
|
first = false;
|
||
|
}
|
||
|
|
||
|
for (coord_t counter = 0; counter < SKELETAL_TRAPEZOIDATION_BEAD_SEARCH_MAX; counter++)
|
||
|
{ // Prevent endless recursion
|
||
|
if (further_edges.empty()) return nullptr;
|
||
|
DistEdge here = further_edges.top();
|
||
|
further_edges.pop();
|
||
|
if (here.dist > max_dist) return nullptr;
|
||
|
if (here.edge_to->to->data.hasBeading())
|
||
|
{
|
||
|
return here.edge_to->to->data.getBeading();
|
||
|
}
|
||
|
else
|
||
|
{ // recurse
|
||
|
for (edge_t* further_edge = here.edge_to->next; further_edge && further_edge != here.edge_to->twin; further_edge = further_edge->twin->next)
|
||
|
{
|
||
|
further_edges.emplace(further_edge, here.dist + (further_edge->to->p - further_edge->from->p).cast<int64_t>().norm());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::addToolpathSegment(const ExtrusionJunction& from, const ExtrusionJunction& to, bool is_odd, bool force_new_path, bool from_is_3way, bool to_is_3way)
|
||
|
{
|
||
|
if (from == to) return;
|
||
|
|
||
|
std::vector<VariableWidthLines> &generated_toolpaths = *p_generated_toolpaths;
|
||
|
|
||
|
size_t inset_idx = from.perimeter_index;
|
||
|
if (inset_idx >= generated_toolpaths.size())
|
||
|
{
|
||
|
generated_toolpaths.resize(inset_idx + 1);
|
||
|
}
|
||
|
assert((generated_toolpaths[inset_idx].empty() || !generated_toolpaths[inset_idx].back().junctions.empty()) && "empty extrusion lines should never have been generated");
|
||
|
if (generated_toolpaths[inset_idx].empty()
|
||
|
|| generated_toolpaths[inset_idx].back().is_odd != is_odd
|
||
|
|| generated_toolpaths[inset_idx].back().junctions.back().perimeter_index != inset_idx // inset_idx should always be consistent
|
||
|
)
|
||
|
{
|
||
|
force_new_path = true;
|
||
|
}
|
||
|
if (!force_new_path
|
||
|
&& shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - from.p, scaled<coord_t>(0.010))
|
||
|
&& std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - from.w) < scaled<coord_t>(0.010)
|
||
|
&& ! from_is_3way // force new path at 3way intersection
|
||
|
)
|
||
|
{
|
||
|
generated_toolpaths[inset_idx].back().junctions.push_back(to);
|
||
|
}
|
||
|
else if (!force_new_path
|
||
|
&& shorter_then(generated_toolpaths[inset_idx].back().junctions.back().p - to.p, scaled<coord_t>(0.010))
|
||
|
&& std::abs(generated_toolpaths[inset_idx].back().junctions.back().w - to.w) < scaled<coord_t>(0.010)
|
||
|
&& ! to_is_3way // force new path at 3way intersection
|
||
|
)
|
||
|
{
|
||
|
if ( ! is_odd)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(error) << "Reversing even wall line causes it to be printed CCW instead of CW!";
|
||
|
}
|
||
|
generated_toolpaths[inset_idx].back().junctions.push_back(from);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
generated_toolpaths[inset_idx].emplace_back(inset_idx, is_odd);
|
||
|
generated_toolpaths[inset_idx].back().junctions.push_back(from);
|
||
|
generated_toolpaths[inset_idx].back().junctions.push_back(to);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void SkeletalTrapezoidation::connectJunctions(ptr_vector_t<LineJunctions>& edge_junctions)
|
||
|
{
|
||
|
EdgeSet unprocessed_quad_starts(graph.edges.size() * 5 / 2);
|
||
|
for (edge_t& edge : graph.edges)
|
||
|
{
|
||
|
if (!edge.prev)
|
||
|
{
|
||
|
unprocessed_quad_starts.emplace(&edge);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
EdgeSet passed_odd_edges;
|
||
|
|
||
|
while (!unprocessed_quad_starts.empty())
|
||
|
{
|
||
|
edge_t* poly_domain_start = *unprocessed_quad_starts.begin();
|
||
|
edge_t* quad_start = poly_domain_start;
|
||
|
bool new_domain_start = true;
|
||
|
do
|
||
|
{
|
||
|
edge_t* quad_end = quad_start;
|
||
|
while (quad_end->next)
|
||
|
{
|
||
|
quad_end = quad_end->next;
|
||
|
}
|
||
|
|
||
|
edge_t* edge_to_peak = getQuadMaxRedgeTo(quad_start);
|
||
|
// walk down on both sides and connect junctions
|
||
|
edge_t* edge_from_peak = edge_to_peak->next; assert(edge_from_peak);
|
||
|
|
||
|
unprocessed_quad_starts.erase(quad_start);
|
||
|
|
||
|
if (! edge_to_peak->data.hasExtrusionJunctions())
|
||
|
{
|
||
|
edge_junctions.emplace_back(std::make_shared<LineJunctions>());
|
||
|
edge_to_peak->data.setExtrusionJunctions(edge_junctions.back());
|
||
|
}
|
||
|
// The junctions on the edge(s) from the start of the quad to the node with highest R
|
||
|
LineJunctions from_junctions = *edge_to_peak->data.getExtrusionJunctions();
|
||
|
if (! edge_from_peak->twin->data.hasExtrusionJunctions())
|
||
|
{
|
||
|
edge_junctions.emplace_back(std::make_shared<LineJunctions>());
|
||
|
edge_from_peak->twin->data.setExtrusionJunctions(edge_junctions.back());
|
||
|
}
|
||
|
// The junctions on the edge(s) from the end of the quad to the node with highest R
|
||
|
LineJunctions to_junctions = *edge_from_peak->twin->data.getExtrusionJunctions();
|
||
|
if (edge_to_peak->prev)
|
||
|
{
|
||
|
LineJunctions from_prev_junctions = *edge_to_peak->prev->data.getExtrusionJunctions();
|
||
|
while (!from_junctions.empty() && !from_prev_junctions.empty() && from_junctions.back().perimeter_index <= from_prev_junctions.front().perimeter_index)
|
||
|
{
|
||
|
from_junctions.pop_back();
|
||
|
}
|
||
|
from_junctions.reserve(from_junctions.size() + from_prev_junctions.size());
|
||
|
from_junctions.insert(from_junctions.end(), from_prev_junctions.begin(), from_prev_junctions.end());
|
||
|
assert(!edge_to_peak->prev->prev);
|
||
|
if(edge_to_peak->prev->prev)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "The edge we're about to connect is already connected.";
|
||
|
}
|
||
|
}
|
||
|
if (edge_from_peak->next)
|
||
|
{
|
||
|
LineJunctions to_next_junctions = *edge_from_peak->next->twin->data.getExtrusionJunctions();
|
||
|
while (!to_junctions.empty() && !to_next_junctions.empty() && to_junctions.back().perimeter_index <= to_next_junctions.front().perimeter_index)
|
||
|
{
|
||
|
to_junctions.pop_back();
|
||
|
}
|
||
|
to_junctions.reserve(to_junctions.size() + to_next_junctions.size());
|
||
|
to_junctions.insert(to_junctions.end(), to_next_junctions.begin(), to_next_junctions.end());
|
||
|
assert(!edge_from_peak->next->next);
|
||
|
if(edge_from_peak->next->next)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "The edge we're about to connect is already connected!";
|
||
|
}
|
||
|
}
|
||
|
assert(std::abs(int(from_junctions.size()) - int(to_junctions.size())) <= 1); // at transitions one end has more beads
|
||
|
if(std::abs(int(from_junctions.size()) - int(to_junctions.size())) > 1)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Can't create a transition when connecting two perimeters where the number of beads differs too much! " << from_junctions.size() << " vs. " << to_junctions.size();
|
||
|
}
|
||
|
|
||
|
size_t segment_count = std::min(from_junctions.size(), to_junctions.size());
|
||
|
for (size_t junction_rev_idx = 0; junction_rev_idx < segment_count; junction_rev_idx++)
|
||
|
{
|
||
|
ExtrusionJunction& from = from_junctions[from_junctions.size() - 1 - junction_rev_idx];
|
||
|
ExtrusionJunction& to = to_junctions[to_junctions.size() - 1 - junction_rev_idx];
|
||
|
assert(from.perimeter_index == to.perimeter_index);
|
||
|
if(from.perimeter_index != to.perimeter_index)
|
||
|
{
|
||
|
BOOST_LOG_TRIVIAL(warning) << "Connecting two perimeters with different indices! Perimeter " << from.perimeter_index << " and " << to.perimeter_index;
|
||
|
}
|
||
|
const bool from_is_odd =
|
||
|
quad_start->to->data.bead_count > 0 && quad_start->to->data.bead_count % 2 == 1 // quad contains single bead segment
|
||
|
&& quad_start->to->data.transition_ratio == 0 // We're not in a transition
|
||
|
&& junction_rev_idx == segment_count - 1 // Is single bead segment
|
||
|
&& shorter_then(from.p - quad_start->to->p, scaled<coord_t>(0.005));
|
||
|
const bool to_is_odd =
|
||
|
quad_end->from->data.bead_count > 0 && quad_end->from->data.bead_count % 2 == 1 // quad contains single bead segment
|
||
|
&& quad_end->from->data.transition_ratio == 0 // We're not in a transition
|
||
|
&& junction_rev_idx == segment_count - 1 // Is single bead segment
|
||
|
&& shorter_then(to.p - quad_end->from->p, scaled<coord_t>(0.005));
|
||
|
const bool is_odd_segment = from_is_odd && to_is_odd;
|
||
|
if (is_odd_segment
|
||
|
&& passed_odd_edges.count(quad_start->next->twin) > 0) // Only generate toolpath for odd segments once
|
||
|
{
|
||
|
continue; // Prevent duplication of single bead segments
|
||
|
}
|
||
|
bool from_is_3way = from_is_odd && quad_start->to->isMultiIntersection();
|
||
|
bool to_is_3way = to_is_odd && quad_end->from->isMultiIntersection();
|
||
|
passed_odd_edges.emplace(quad_start->next);
|
||
|
|
||
|
addToolpathSegment(from, to, is_odd_segment, new_domain_start, from_is_3way, to_is_3way);
|
||
|
}
|
||
|
new_domain_start = false;
|
||
|
}
|
||
|
while(quad_start = quad_start->getNextUnconnected(), quad_start != poly_domain_start);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void SkeletalTrapezoidation::generateLocalMaximaSingleBeads()
|
||
|
{
|
||
|
std::vector<VariableWidthLines> &generated_toolpaths = *p_generated_toolpaths;
|
||
|
|
||
|
for (auto& node : graph.nodes)
|
||
|
{
|
||
|
if (! node.data.hasBeading())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
Beading& beading = node.data.getBeading()->beading;
|
||
|
if (beading.bead_widths.size() % 2 == 1 && node.isLocalMaximum(true) && !node.isCentral())
|
||
|
{
|
||
|
const size_t inset_index = beading.bead_widths.size() / 2;
|
||
|
constexpr bool is_odd = true;
|
||
|
if (inset_index >= generated_toolpaths.size())
|
||
|
{
|
||
|
generated_toolpaths.resize(inset_index + 1);
|
||
|
}
|
||
|
generated_toolpaths[inset_index].emplace_back(inset_index, is_odd);
|
||
|
ExtrusionLine& line = generated_toolpaths[inset_index].back();
|
||
|
const coord_t width = beading.bead_widths[inset_index];
|
||
|
// total area to be extruded is pi*(w/2)^2 = pi*w*w/4
|
||
|
// Width a constant extrusion width w, that would be a length of pi*w/4
|
||
|
// If we make a small circle to fill up the hole, then that circle would have a circumference of 2*pi*r
|
||
|
// So our circle needs to be such that r=w/8
|
||
|
const coord_t r = width / 8;
|
||
|
constexpr coord_t n_segments = 6;
|
||
|
for (coord_t segment = 0; segment < n_segments; segment++) {
|
||
|
float a = 2.0 * M_PI / n_segments * segment;
|
||
|
line.junctions.emplace_back(node.p + Point(r * cos(a), r * sin(a)), width, inset_index);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// ^^^^^^^^^^^^^^^^^^^^^
|
||
|
// TOOLPATH GENERATION
|
||
|
// =====================
|
||
|
//
|
||
|
|
||
|
} // namespace Slic3r::Arachne
|