diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index b1f9861a7a..1135f71ab8 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -1922,1439 +1922,3 @@ TEST_CASE("Voronoi skeleton", "[VoronoiSkeleton]") REQUIRE(! skeleton_edges.empty()); } - -// hold data about skeleton Graph -struct VoronoiGraph -{ - struct Node; - using Nodes = std::vector; - struct Path; - struct ExPath; - using Circle = Path; - std::map data; -}; - -/// -/// Node data structure for Voronoi Graph. -/// Extend information about Voronoi vertex. -/// -struct VoronoiGraph::Node -{ - // reference to Voronoi diagram VertexCategory::Inside OR - // VertexCategory::OnContour but NOT VertexCategory::Outside - const VD::vertex_type *vertex; - // longest distance to edge sum of line segment size (no euclid because of shape U) - double longestDistance; - - // actual distance to edge - double distance; - - struct Neighbor; - std::vector neighbors; - - // constructor - Node(const VD::vertex_type *vertex, double distance) - : vertex(vertex), longestDistance(0.), distance(distance), neighbors() - {} -}; - -// return node from graph by vertex, when no exists create one -VoronoiGraph::Node *getNode( - VoronoiGraph& graph, - const VD::vertex_type *vertex, - const VD::edge_type * edge, - const Lines & lines) -{ - std::map &data = graph.data; - auto &mapItem = data.find(vertex); - // return when exists - if (mapItem != data.end()) return &mapItem->second; - - // is new vertex (first edge to this vertex) - // calculate distance to islad border + fill item0 - const VD::cell_type *cell = edge->cell(); - // const VD::cell_type * cell2 = edge.twin()->cell(); - const Line &line = lines[cell->source_index()]; - // const Line & line1 = lines[cell2->source_index()]; - Point point(vertex->x(), vertex->y()); - double distance = line.distance_to(point); - - auto& [iterator,success] = data.emplace(vertex, VoronoiGraph::Node(vertex, distance)); - assert(success); - return &iterator->second; -} - -/// -/// Surrond GraphNode data type. -/// Extend information about voronoi edge. -/// -struct VoronoiGraph::Node::Neighbor -{ - const VD::edge_type *edge; - // length edge between vertices - double edge_length; - // pointer on graph node structure - Node *node; - -public: - Neighbor(const VD::edge_type *edge, - double edge_length, - Node *node) - : edge(edge) - , edge_length(edge_length) - , node(node) - {} -}; - -/// -/// Data object represents path over nodes of VoronoiGraph -/// store their length of path -/// -struct VoronoiGraph::Path -{ - // row of neighbor Nodes - VoronoiGraph::Nodes path; // TODO: rename to nodes - - // length of path - // when circle contain length from back to front; - double length; -public: - Path() : path(), length(0.) {} - Path(const VoronoiGraph::Node *node) : path({node}), length(0.) {} - Path(VoronoiGraph::Nodes nodes, double length) - : path(std::move(nodes)), length(length) - {} - - void append(const VoronoiGraph::Node *node, double length) { - path.push_back(node); - this->length += length; - } -}; - -struct OrderLengthFromShortest -{ - bool operator()(const VoronoiGraph::Path &path1, - const VoronoiGraph::Path &path2) - { - return path1.length > path2.length; - } -}; - -struct OrderLengthFromLongest -{ - bool operator()(const VoronoiGraph::Path &path1, - const VoronoiGraph::Path &path2) - { - return path1.length < path2.length; - } -}; - - -/// -/// Data object -/// extends path with circles and side branches -/// -struct VoronoiGraph::ExPath : public VoronoiGraph::Path -{ - // not main path is stored in secondary paths - // key is pointer to source node - using SideBranches = std::priority_queue< - VoronoiGraph::Path, std::vector, OrderLengthFromLongest>; - using SideBranchesMap = std::map; - - // All side branches in Graph under node - // Map contains only node, which has side branche(s) - // There is NOT empty SideBranches in map !!! - SideBranchesMap side_branches; - - // All circles in Graph under node - std::vector circles; - - // alone circle does'n have record in connected_circle - // every connected circle have at least two records(firs to second & second to first) - // EXAMPLE with 3 circles(index to circles stored in this->circles are: c1, c2 and c3) connected together - // connected_circle[c1] = {c2, c3}; - // connected_circle[c2] = {c1, c3}; - // connected_circle[c3] = {c1, c2}; - std::map> connected_circle; -public: - ExPath() = default; -}; - -/// -/// calculate distances to border of island and length on skeleton -/// -/// Input anotated voronoi diagram(use function Slic3r::Voronoi::annotate_inside_outside) -/// Source lines for voronoi diagram -/// Extended voronoi graph by distances and length -VoronoiGraph getSkeleton(const VD &vd, const Lines &lines) -{ - // vd should be annotated. - //assert(Voronoi::debug::verify_inside_outside_annotations(vd)); - - VoronoiGraph skeleton; - const VD::edge_type *first_edge = &vd.edges().front(); - for (const VD::edge_type &edge : vd.edges()) - { - size_t edge_idx = &edge - first_edge; - if ( - // Ignore secondary and unbounded edges, they shall never be part - // of the skeleton. - edge.is_secondary() || edge.is_infinite() || - // Skip the twin edge of an edge, that has already been processed. - &edge > edge.twin() || - // Ignore outer edges. - (Voronoi::edge_category(edge) != Voronoi::EdgeCategory::PointsInside && - Voronoi::edge_category(edge.twin()) != Voronoi::EdgeCategory::PointsInside)) - continue; - - const VD::vertex_type *v0 = edge.vertex0(); - const VD::vertex_type *v1 = edge.vertex1(); - Voronoi::VertexCategory category0 = Voronoi::vertex_category(*v0); - Voronoi::VertexCategory category1 = Voronoi::vertex_category(*v1); - if (category0 == Voronoi::VertexCategory::Outside || - category1 == Voronoi::VertexCategory::Outside) - continue; - // only debug check annotation - if (category0 == Voronoi::VertexCategory::Unknown || - category1 == Voronoi::VertexCategory::Unknown) - return {}; // vd must be annotated - - double length = 0; - if (edge.is_linear()) { - double diffX = v0->x() - v1->x(); - double diffY = v0->y() - v1->y(); - length = sqrt(diffX * diffX + diffY * diffY); - } else { // if (edge.is_curved()) - // TODO: len of parabola - length = 1.0; - } - - VoronoiGraph::Node* node0 = getNode(skeleton, v0, &edge, lines); - VoronoiGraph::Node* node1 = getNode(skeleton, v1, &edge, lines); - - // add extended Edge to graph, both side - VoronoiGraph::Node::Neighbor neighbor0(&edge, length, node1); - node0->neighbors.push_back(neighbor0); - VoronoiGraph::Node::Neighbor neighbor1(edge.twin(), length, node0); - node1->neighbors.push_back(neighbor1); - } - return skeleton; -} - -/// -/// Configuration fro sampling voronoi diagram for support point generator -/// -struct SampleConfig -{ - // Maximal distance from edge - double max_distance = 1.; // must be bigger than zero - // Maximal distance between samples on skeleton - double sample_size = 1.; // must be bigger than zero - // distance from edge of skeleton - double start_distance = 0; // support head diameter - - // maximal length of longest path in voronoi diagram to be island supported only by one single support point - // this point will be in center of path - // suggestion: smaller than 2* SampleConfig.start_distance - double max_length_for_one_support_point = 1.; - - // each curve is sampled by this value to test distance to edge of island - double curve_sample = 1.; // must be bigger than zero -}; - -/// -/// PRIVATE -/// find neighbor and return distance between nodes -/// -/// source of neighborse -/// neighbor node -/// When neighbor return distance between neighbor Nodes otherwise no value -const VoronoiGraph::Node::Neighbor* get_neighbor( - const VoronoiGraph::Node *from, - const VoronoiGraph::Node *to) -{ - for (const VoronoiGraph::Node::Neighbor &neighbor : from->neighbors) - if (neighbor.node == to) return &neighbor; - return nullptr; -} -double get_neighbor_distance(const VoronoiGraph::Node *from, - const VoronoiGraph::Node *to) -{ - const VoronoiGraph::Node::Neighbor *neighbor = get_neighbor(from, to); - assert(neighbor != nullptr); - return neighbor->edge_length; -} - -/// -/// PRIVATE: -/// fix longest distance after circle -/// -/// Circle needs to be fix length -/// Circle side branches -/// Path before circle -/// Longest nodes path and its length -VoronoiGraph::Path find_longest_path_on_circle( - const VoronoiGraph::Circle &circle, - const VoronoiGraph::ExPath::SideBranchesMap &side_branches) -{ - double half_circle_length = circle.length / 2.; - double distance_on_circle = 0; - - bool is_longest_revers_direction = false; - const VoronoiGraph::Node *longest_circle_node = nullptr; - const VoronoiGraph::Path *longest_circle_branch = nullptr; - double longest_branch_length = 0; - - bool is_short_revers_direction = false; - // find longest side branch - const VoronoiGraph::Node *prev_circle_node = nullptr; - for (const VoronoiGraph::Node *circle_node: circle.path) { - if (prev_circle_node != nullptr) - distance_on_circle += get_neighbor_distance(circle_node, prev_circle_node); - prev_circle_node = circle_node; - - auto side_branches_item = side_branches.find(circle_node); - if (side_branches_item != side_branches.end()) { - // side_branches should be sorted by length - if (distance_on_circle > half_circle_length) - is_short_revers_direction = true; - const auto &longest_node_branch = side_branches_item->second.top(); - double circle_branch_length = longest_node_branch.length + - ((is_short_revers_direction) ? - (circle.length - - distance_on_circle) : - distance_on_circle); - if (longest_branch_length < circle_branch_length) { - longest_branch_length = circle_branch_length; - is_longest_revers_direction = is_short_revers_direction; - longest_circle_node = circle_node; - longest_circle_branch = &longest_node_branch; - } - } - } - assert(longest_circle_node != nullptr); // only circle with no side branches - assert(longest_circle_branch != nullptr); - // almost same - double preccission - //distance_on_circle += get_neighbor_distance(circle.path.back(), circle.path.front()); - //assert(distance_on_circle == circle.length); - - // circlePath - auto circle_iterator = std::find(circle.path.begin(), circle.path.end(), longest_circle_node); - VoronoiGraph::Nodes circle_path; - if (is_longest_revers_direction) { - circle_path = VoronoiGraph::Nodes( - circle_iterator, - circle.path.end()); - std::reverse(circle_path.begin(), circle_path.end()); - } else { - if (longest_circle_node != circle.path.front()) - circle_path = VoronoiGraph::Nodes( - circle.path.begin() + 1, - circle_iterator+1); - } - // append longest side branch - circle_path.insert(circle_path.end(), - longest_circle_branch->path.begin(), - longest_circle_branch->path.end()); - return {circle_path, longest_branch_length}; -} - -/// -/// PRIVATE: -/// Serach longest path from input_node throw Nodes in connected circles -/// -/// Node on circle -/// index of circle with input node -/// Hold Circles, connection of circles and Side branches -/// Longest path from input node -VoronoiGraph::Path find_longest_path_on_circles( - const VoronoiGraph::Node &input_node, - size_t finished_circle_index, - const VoronoiGraph::ExPath &ex_path) -{ - const std::vector &circles = ex_path.circles; - const auto &circle = circles[finished_circle_index]; - auto connected_circle_item = ex_path.connected_circle.find(finished_circle_index); - // is only one circle - if (connected_circle_item == ex_path.connected_circle.end()) { - // find longest path over circle and store it into next_path - return find_longest_path_on_circle(circle, ex_path.side_branches); - } - - // multi circle - // find longest path over circles - const std::set &connected_circles = connected_circle_item->second; - - // collect all circle ndoes - std::set nodes; - nodes.insert(circle.path.begin(), circle.path.end()); - for (size_t circle_index : connected_circles) { - const auto &circle = circles[circle_index]; - nodes.insert(circle.path.begin(), circle.path.end()); - } - - // nodes are path throw circles - // length is sum path throw circles PLUS length of longest side_branch - VoronoiGraph::Path longest_path; - - // wide search by shortest distance for path over circle's node - // !! Do NOT use recursion, may cause stack overflow - std::set done; // all ready checked - // on top is shortest path - std::priority_queue, - OrderLengthFromShortest> search_queue; - VoronoiGraph::Path start_path({&input_node}, 0.); - search_queue.emplace(start_path); - while (!search_queue.empty()) { - // shortest path from input_node - VoronoiGraph::Path path(std::move(search_queue.top())); - search_queue.pop(); - const VoronoiGraph::Node &node = *path.path.back(); - if (done.find(&node) != done.end()) { // already checked - continue; - } - done.insert(&node); - for (const VoronoiGraph::Node::Neighbor &neighbor : node.neighbors) { - if (nodes.find(neighbor.node) == nodes.end()) continue; // out of circles - if (done.find(neighbor.node) != done.end()) continue; - VoronoiGraph::Path neighbor_path = path; // make copy - neighbor_path.append(neighbor.node, neighbor.edge_length); - search_queue.push(neighbor_path); - - auto branches_item = ex_path.side_branches.find(neighbor.node); - // exist side from this neighbor node ? - if (branches_item == ex_path.side_branches.end()) continue; - const VoronoiGraph::Path &longest_branch = - branches_item->second.top(); - double length = longest_branch.length + neighbor_path.length; - if (longest_path.length < length) { - longest_path.length = length; - longest_path.path = neighbor_path.path; // copy path - } - } - } - - // create result path - assert(!longest_path.path.empty()); - longest_path.path.erase(longest_path.path.begin()); // remove input_node - assert(!longest_path.path.empty()); - auto branches_item = ex_path.side_branches.find(longest_path.path.back()); - if (branches_item == ex_path.side_branches.end()) { - // longest path ends on circle - return longest_path; - } - const VoronoiGraph::Path &longest_branch = branches_item->second.top(); - longest_path.path.insert(longest_path.path.end(), - longest_branch.path.begin(), - longest_branch.path.end()); - return longest_path; -} - -/// -/// PRIVATE: -/// function for detection circle in passed path -/// -/// IN/OUT: If exist append circle to path -/// neighbor possible created circle -/// TRUE when circle was created otherwise FALSE -std::optional create_circle( - const VoronoiGraph::Path &path, - const VoronoiGraph::Node::Neighbor &neighbor) -{ - VoronoiGraph::Nodes passed_nodes = path.path; - // detection of circle - // not neccesary to check last one in path - auto end_find = passed_nodes.end() - 1; - const auto &path_item = std::find(passed_nodes.begin(), end_find, - neighbor.node); - if (path_item == end_find) return {}; // circle not detected - // separate Circle: - VoronoiGraph::Nodes circle_path(path_item, passed_nodes.end()); - // !!! Real circle lenght is calculated on detection of end circle - // now circle_length contain also lenght of path before circle - double circle_length = path.length + neighbor.edge_length; - // solve of branch length will be at begin of cirlce - return VoronoiGraph::Circle(std::move(circle_path), circle_length); -}; - -/// -/// PRIVATE: -/// merge connected circle structures -/// -/// -/// -/// Count of destination circles before merge -/// Source circle are append afted destination, -/// therfore all src indexes must be increased by destination circle count -void merge_connected_circle( - std::map>& dst, - std::map>& src, - size_t dst_circle_count) -{ - std::set done; - for (const auto &item : src) { - size_t dst_index = dst_circle_count + item.first; - if (done.find(dst_index) != done.end()) continue; - done.insert(dst_index); - - std::set connected_circle; - for (const size_t &src_index : item.second) - connected_circle.insert(dst_circle_count + src_index); - - auto &dst_set = dst[dst_index]; - dst_set.merge(connected_circle); - - // write same information into connected circles - connected_circle = dst_set; // copy - connected_circle.insert(dst_index); - for (size_t prev_connection_idx : dst_set) { - done.insert(prev_connection_idx); - for (size_t connected_circle_idx : connected_circle) { - if (connected_circle_idx == prev_connection_idx) continue; - dst[prev_connection_idx].insert(connected_circle_idx); - } - } - } -} - -/// -/// PRIVATE: -/// move data from source to destination -/// side_branches + circles + connected_circle -/// -/// result path - destination -/// source path - data will be moved to dst -void append_neighbor_branch(VoronoiGraph::ExPath &dst, - VoronoiGraph::ExPath &src) -{ - // move side branches - if (!src.side_branches.empty()) - dst.side_branches.insert( - std::make_move_iterator(src.side_branches.begin()), - std::make_move_iterator(src.side_branches.end())); - - // move circles - if (!src.circles.empty()) { - // copy connected circles indexes - if (!src.connected_circle.empty()) { - merge_connected_circle(dst.connected_circle, src.connected_circle, - dst.circles.size()); - } - dst.circles.insert( - dst.circles.end(), - std::make_move_iterator(src.circles.begin()), - std::make_move_iterator(src.circles.end())); - - } -} - -/// -/// DTO for process node during depth search of longest path in voronoi graph -/// -struct NodeData -{ - // actual proccessed node - const VoronoiGraph::Node *node; - // distance to this node from input node - double distance_to_node; - - // path from start point to this node - // last one is actual node - VoronoiGraph::Path act_path; - - // skip node when circle start - must end at this node - // set --> multiple cirle could start at same node - // previous node should be skiped to so it is initialized with it - std::set skip_nodes; // circle - - // store all circle indexes this node is lay on - // used to create connected circles structure - std::vector circle_indexes; - // When circle_indexes is not empty node lays on circle - // and in this node is not searching for longest path only store side - // branches(not part of circle) - - // indexes of circle ending in this node(could be more than one) - std::vector end_circle_indexes; - // When end_circle_indexes == circle_indexes - // than it is end of circle (multi circle structure) and it is processed - - // contain possible continue path - // possible empty - VoronoiGraph::ExPath::SideBranches side_branches; -public: - // append node to act path - NodeData(const VoronoiGraph::Node *node, - double distance_to_node, - const VoronoiGraph::Path &prev_path) - : node(node), distance_to_node(distance_to_node) - { - const VoronoiGraph::Node *prev_node = (prev_path.path.size() >= 1) ? - prev_path.path.back() : - nullptr; - skip_nodes = {prev_node}; - // append actual node - act_path = prev_path; // copy - act_path.append(node, distance_to_node); // increase path - } -}; - -/// -/// PRIVATE: -/// Depth search in Voronoi graph for longest path -/// -/// !! not work on circles(island with holes) -/// !! need restructuralization by branch from start path -/// !!! RECURRENT cause stack overflow -/// -/// IN/OUT path in Graph -VoronoiGraph::ExPath create_longest_branch_from_node( - const VoronoiGraph::Node &node, - double distance_to_node = 0., - const VoronoiGraph::Path &prev_path = VoronoiGraph::Path({},0.)) -{ - // keep data out of stack - std::unique_ptr data = std::make_unique( - &node, distance_to_node, prev_path); - VoronoiGraph::ExPath result; - // Depth search for path of all neighbors --> fill paths - for (const VoronoiGraph::Node::Neighbor &neighbor : node.neighbors) { - if (data->skip_nodes.find(neighbor.node) != data->skip_nodes.end()) - continue; - - // detection of circle - auto circle_opt = create_circle(data->act_path, neighbor); - if (circle_opt.has_value()) { - size_t circle_index = result.circles.size(); - data->circle_indexes.push_back(circle_index); - result.circles.emplace_back(std::move(circle_opt.value())); - continue; - } - - // create copy of path(not circles, not side_branches) - const VoronoiGraph::Node &next_node = *neighbor.node; - // is next node leaf ? - if (next_node.neighbors.size() == 1) { - VoronoiGraph::Path side_branch({&next_node}, neighbor.edge_length); - data->side_branches.push(std::move(side_branch)); - continue; - } - - VoronoiGraph::ExPath next_path = - create_longest_branch_from_node(next_node, neighbor.edge_length, - data->act_path); - - bool is_circle_neighbor = false; - if (next_path.path.empty()) { // neighbor node is on circle - for (VoronoiGraph::Circle &circle : next_path.circles) { - const auto &circle_item = std::find(circle.path.begin(), - circle.path.end(), &node); - if (circle_item == circle.path.end()) continue; // node is NOT on circle - - size_t next_circle_index = &circle - &next_path.circles.front(); - size_t circle_index = result.circles.size() + next_circle_index; - data->circle_indexes.push_back(circle_index); - - // check if this node is end of circle - if (circle_item == circle.path.begin()) { - data->end_circle_indexes.push_back(circle_index); - - // !! this FIX circle lenght because at detection of - // circle it will cost time to calculate it - circle.length -= data->act_path.length; - - // skip twice checking of circle - data->skip_nodes.insert(circle.path.back()); - } - is_circle_neighbor = true; - } - } - - append_neighbor_branch(result, next_path); - - if (!is_circle_neighbor) - data->side_branches.push(std::move(next_path)); - } - - // remember connected circle - if (data->circle_indexes.size() > 1) { - for (size_t circle_index : data->circle_indexes) { - for (size_t circle_index2 : data->circle_indexes) { - if (circle_index == circle_index2) continue; - result.connected_circle[circle_index].insert(circle_index2); - } - } - } - - // detect end of circles in this node - if (!data->end_circle_indexes.empty() && - data->end_circle_indexes.size() == data->circle_indexes.size()) { - // possible any of circle indexes - size_t circle_index = data->circle_indexes.front(); - data->side_branches.push( - find_longest_path_on_circles(node, circle_index, result)); - - data->circle_indexes.clear(); // resolved circles - } - - // simple node on circle --> only input and output neighbor - if (data->side_branches.empty()) { - return result; - } - - // is node on unresolved circle? - if (!data->circle_indexes.empty()) { - // not search for longest path, it will eval on end of circle - result.side_branches[&node] = data->side_branches; - return result; - } - - // create result longest path from longest side branch - VoronoiGraph::Path longest_path(std::move(data->side_branches.top())); - data->side_branches.pop(); - if (!data->side_branches.empty()) { - result.side_branches[&node] = data->side_branches; - } - longest_path.path.insert(longest_path.path.begin(), &node); - result.path = std::move(longest_path.path); - result.length = distance_to_node + longest_path.length; - return result; -} -// only for debug purpose -double get_length(VoronoiGraph::Nodes &nodes) { - double length = 0; - const VoronoiGraph::Node *prev_node = nullptr; - for (const VoronoiGraph::Node *node : nodes) { - if (prev_node != nullptr) { - length += get_neighbor_distance(prev_node, node); - } - prev_node = node; - } - return length; -} - -/// -/// PRIVATE: -/// Restructuralize path by stored branches created from random point search longest path -/// When exist longer path swap branches -/// -/// IN/OUT path to be fixed after creating longest path from one point -void reshape_longest_path(VoronoiGraph::ExPath& path) { - assert(path.path.size() >= 1); - - double actual_length = 0.; - const VoronoiGraph::Node *prev_node = nullptr; - VoronoiGraph::Nodes origin_path = path.path; // make copy - // index to path - size_t path_index = 0; - for (const VoronoiGraph::Node *node : origin_path) { - if (prev_node != nullptr) { - ++path_index; - actual_length += get_neighbor_distance(prev_node, node); - } - prev_node = node; - // increase actual length - - auto side_branches_item = path.side_branches.find(node); - if (side_branches_item == path.side_branches.end()) - continue; // no side branches - VoronoiGraph::ExPath::SideBranches &branches = side_branches_item->second; - if (actual_length >= branches.top().length) - continue; // no longer branch - - auto end_path = path.path.begin() + path_index; - VoronoiGraph::Path side_branch({path.path.begin(), end_path}, - actual_length); - std::reverse(side_branch.path.begin(), side_branch.path.end()); - VoronoiGraph::Path new_main_branch(std::move(branches.top())); - branches.pop(); - std::reverse(new_main_branch.path.begin(), new_main_branch.path.end()); - // add old main path store into side branches - may be it is not neccessary - branches.push(std::move(side_branch)); - - // swap side branch with main branch - path.path.erase(path.path.begin(), end_path); - path.path.insert(path.path.begin(), new_main_branch.path.begin(), - new_main_branch.path.end()); - - path.length += new_main_branch.length; - path.length -= actual_length; - path_index = new_main_branch.path.size(); - actual_length = new_main_branch.length; - } -} - -/// -/// DTO for non recursive call to search of longest path -/// -struct NodeDataWithResult : public NodeData -{ - // result for this node - VoronoiGraph::ExPath &result; - -public: - NodeDataWithResult(VoronoiGraph::ExPath & result, - const VoronoiGraph::Node *node, - double distance_to_node, - const VoronoiGraph::Path &prev_path) - : result(result), NodeData(node, distance_to_node, prev_path) - {} -}; - -/// -/// interface for objects inside of CallStack -/// -class IStackFunction -{ -public: - using CallStack = std::stack>; - virtual ~IStackFunction() = default; - virtual void process(CallStack& call_stack) = 0; -}; - -class PostProcessNeighbors; - -/// -/// create on stack -/// 1 * PostProcessNeighbors -/// N * ExpandNode -/// -class EvaluateNeighbor : public IStackFunction -{ - std::unique_ptr post_process_neighbor; -public: - EvaluateNeighbor( - VoronoiGraph::ExPath & result, - const VoronoiGraph::Node *node, - double distance_to_node = 0., - const VoronoiGraph::Path &prev_path = VoronoiGraph::Path({}, 0.)); - virtual void process(CallStack &call_stack); -}; - -/// -/// call after all neighbors are processed -/// -class PostProcessNeighbors : public NodeDataWithResult , public IStackFunction -{ -public: - PostProcessNeighbors( - VoronoiGraph::ExPath & result, - const VoronoiGraph::Node *node, - double distance_to_node = 0., - const VoronoiGraph::Path& prev_path = VoronoiGraph::Path({}, 0.) // make copy - ): NodeDataWithResult(result, node, distance_to_node, prev_path) - {} - - virtual void process([[maybe_unused]] CallStack &call_stack) - { - process(); - } - - void process() - { - // remember connected circle - if (circle_indexes.size() > 1) { - for (size_t circle_index : circle_indexes) { - for (size_t circle_index2 : circle_indexes) { - if (circle_index == circle_index2) continue; - result.connected_circle[circle_index].insert( - circle_index2); - } - } - } - - // detect end of circles in this node - if (!end_circle_indexes.empty() && - end_circle_indexes.size() == circle_indexes.size()) { - size_t circle_index = circle_indexes - .front(); // possible any of them - side_branches.push( - find_longest_path_on_circles(*node, circle_index, result)); - - circle_indexes.clear(); // resolved circles - } - - // simple node on circle --> only input and output neighbor - if (side_branches.empty()) return; - - // is node on unresolved circle? - if (!circle_indexes.empty()) { - // not search for longest path, it will eval on end of circle - result.side_branches[node] = side_branches; - return; - } - - // create result longest path from longest side branch - VoronoiGraph::Path longest_path(std::move(side_branches.top())); - side_branches.pop(); - if (!side_branches.empty()) { - result.side_branches[node] = side_branches; - } - longest_path.path.insert(longest_path.path.begin(), node); - result.path = std::move(longest_path.path); - result.length = distance_to_node + longest_path.length; - } -}; - -/// -/// Decimate data from Ex path to path -/// Done after ONE neighbor is procceessed. -/// Check if node is on circle. -/// Remember ended circle -/// Merge side branches and circle information into result -/// -class PostProcessNeighbor : public IStackFunction -{ - NodeDataWithResult &data; -public: - VoronoiGraph::ExPath neighbor_path; // data filled in EvaluateNeighbor - PostProcessNeighbor(NodeDataWithResult &data): data(data) {} - - virtual void process([[maybe_unused]] CallStack &call_stack) - { - process(); - } - - void process(){ - bool is_circle_neighbor = false; - if (neighbor_path.path.empty()) { // neighbor node is on circle - for (VoronoiGraph::Circle &circle : neighbor_path.circles) { - const auto &circle_item = std::find(circle.path.begin(), - circle.path.end(), data.node); - if (circle_item == circle.path.end()) - continue; // node is NOT on circle - - size_t next_circle_index = &circle - - &neighbor_path.circles.front(); - size_t circle_index = data.result.circles.size() + - next_circle_index; - data.circle_indexes.push_back(circle_index); - - // check if this node is end of circle - if (circle_item == circle.path.begin()) { - data.end_circle_indexes.push_back(circle_index); - - // !! this FIX circle lenght because at detection of - // circle it will cost time to calculate it - circle.length -= data.act_path.length; - - // skip twice checking of circle - data.skip_nodes.insert(circle.path.back()); - } - is_circle_neighbor = true; - } - } - append_neighbor_branch(data.result, neighbor_path); - if (!is_circle_neighbor) - data.side_branches.push(std::move(neighbor_path)); - } -}; - -class ExpandNeighbor: public IStackFunction -{ - NodeDataWithResult &data; - const VoronoiGraph::Node::Neighbor &neighbor; - -public: - ExpandNeighbor(NodeDataWithResult & data, - const VoronoiGraph::Node::Neighbor &neighbor) - : data(data), neighbor(neighbor) - {} - - virtual void process(CallStack &call_stack) - { - if (data.skip_nodes.find(neighbor.node) != data.skip_nodes.end()) - return; - - // detection of circle - auto circle_opt = create_circle(data.act_path, neighbor); - if (circle_opt.has_value()) { - size_t circle_index = data.result.circles.size(); - data.circle_indexes.push_back(circle_index); - data.result.circles.emplace_back(std::move(circle_opt.value())); - return; - } - - // create copy of path(not circles, not side_branches) - const VoronoiGraph::Node &next_node = *neighbor.node; - // is next node leaf ? - if (next_node.neighbors.size() == 1) { - VoronoiGraph::Path side_branch({&next_node}, neighbor.edge_length); - data.side_branches.push(std::move(side_branch)); - return; - } - - auto post_process_neighbor = std::make_unique( - data); - VoronoiGraph::ExPath &neighbor_path = post_process_neighbor - ->neighbor_path; - - call_stack.emplace(std::move(post_process_neighbor)); - call_stack.emplace( - std::make_unique( - neighbor_path, - neighbor.node, - neighbor.edge_length, - data.act_path)); - } -}; - - -EvaluateNeighbor::EvaluateNeighbor(VoronoiGraph::ExPath & result, - const VoronoiGraph::Node *node, - double distance_to_node, - const VoronoiGraph::Path &prev_path) - : post_process_neighbor( - std::make_unique(result, - node, - distance_to_node, - prev_path)) -{} - -void EvaluateNeighbor::process(IStackFunction::CallStack &call_stack) -{ - NodeDataWithResult &data = *post_process_neighbor; - call_stack.emplace(std::move(post_process_neighbor)); - for (const VoronoiGraph::Node::Neighbor &neighbor : data.node->neighbors) - call_stack.emplace(std::make_unique(data, neighbor)); -} - -/// -/// PRIVATE: -/// Extract the longest path from voronoi graph -/// Restructuralize path by branch created from random point -/// -/// IN/OUT path in Graph -VoronoiGraph::ExPath create_longest_path(const VoronoiGraph::Node *start_node) -{ - VoronoiGraph::ExPath longest_path; - IStackFunction::CallStack call_stack; - call_stack.emplace(std::make_unique(longest_path, start_node)); - - // depth search for longest path in graph - while (!call_stack.empty()) { - std::unique_ptr stack_function = std::move(call_stack.top()); - call_stack.pop(); - stack_function->process(call_stack); - // stack function deleted - } - - reshape_longest_path(longest_path); - // after reshape it shoud be longest path for whole Voronoi Graph - return longest_path; -} - -/// -/// PRIVATE: -/// Extract the longest path from voronoi graph -/// Restructuralize path by branch created from random point -/// -/// IN/OUT path in Graph -VoronoiGraph::ExPath create_longest_path_recursive(const VoronoiGraph::Node *start_node) -{ - VoronoiGraph::ExPath longest_path = create_longest_branch_from_node(*start_node); - reshape_longest_path(longest_path); - // after reshape it shoud be longest path for whole Voronoi Graph - return longest_path; -} - -/// -/// For generating start point -/// -/// Point with only one neighbor -/// -/// -Point get_offseted_point(const VoronoiGraph::Node &node, double padding) -{ - assert(node.neighbors.size() == 1); - const VoronoiGraph::Node::Neighbor &neighbor = node.neighbors.front(); - const VD::edge_type & edge = *neighbor.edge; - const VD::vertex_type & v0 = *edge.vertex0(); - const VD::vertex_type & v1 = *edge.vertex1(); - Point dir(v0.x() - v1.x(), v0.y() - v1.y()); - if (node.vertex == &v0) - dir *= -1; - else - assert(node.vertex == &v1); - - double size = neighbor.edge_length / padding; - Point move(dir[0] / size, dir[1] / size); - return Point(node.vertex->x() + move[0], node.vertex->y() + move[1]); -} - -/// -/// Create point on edge defined by neighbor -/// in distance defined by edge length ratio -/// -Point get_edge_point(const VD::edge_type *edge, double ratio) -{ - const VD::vertex_type *v0 = edge->vertex0(); - const VD::vertex_type *v1 = edge->vertex1(); - if (ratio <= std::numeric_limits::epsilon()) - return Point(v0->x(), v0->y()); - if (ratio >= 1. - std::numeric_limits::epsilon()) - return Point(v1->x(), v1->y()); - - if (edge->is_linear()) { - Point dir( - v1->x() - v0->x(), - v1->y() - v0->y() - ); - // normalize - dir *= ratio; - return Point(v0->x() + dir.x(), v0->y() + dir.y()); - } - - assert(edge->is_curved()); - // TODO: distance on curve - return Point(v0->x(), v0->y()); -} - -Point get_center_of_path(const VoronoiGraph::Nodes& path, double path_length) -{ - const VoronoiGraph::Node *prev_node = nullptr; - double half_path_length = path_length / 2.; - double distance = 0.; - for (const VoronoiGraph::Node *node : path) { - if (prev_node == nullptr) { // first call - prev_node = node; - continue; - } - const VoronoiGraph::Node::Neighbor *neighbor = - get_neighbor(prev_node, node); - distance += neighbor->edge_length; - if (distance >= half_path_length) { - // over half point is on - double ratio = 1. - (distance - half_path_length) / neighbor->edge_length; - return get_edge_point(neighbor->edge, ratio); - } - prev_node = node; - } - // half_path_length must be inside path - // this means bad input params - assert(false); - return Point(0, 0); -} - -/// -/// Sample voronoi skeleton -/// -/// Inside skeleton of island -/// Params for sampling -/// OUTPUT: longest path in graph -/// Vector of sampled points or Empty when distance from edge is bigger than max_distance -std::vector sample_voronoi_graph(const VoronoiGraph &graph, - const SampleConfig &config, - VoronoiGraph::ExPath &longest_path) -{ - // first vertex on contour: - const VoronoiGraph::Node *start_node = nullptr; - for (const auto &[key, value]: graph.data) { - const VD::vertex_type& vertex = *key; - Voronoi::VertexCategory category = Voronoi::vertex_category(vertex); - if (category == Voronoi::VertexCategory::OnContour) { - start_node = &value; - break; - } - } - // every island has to have a point on contour - assert(start_node != nullptr); - - longest_path = create_longest_path(start_node); - //longest_path = create_longest_path_recursive(start_node); - if (longest_path.length < config.max_length_for_one_support_point) - { // create only one point in center - // sample in center of voronoi - return {get_center_of_path(longest_path.path, longest_path.length)}; - } - - std::vector points; - points.push_back(get_offseted_point(*start_node, config.start_distance)); - - return points; -} - -void draw(SVG &svg, const VoronoiGraph &graph, coord_t width) -{ - for (const auto &[key, value]: graph.data) { - svg.draw(Point(key->x(), key->y()), "lightgray",width); - for (const auto& n : value.neighbors) { - if (n.edge->vertex0() > n.edge->vertex1()) continue; - auto v0 = *n.edge->vertex0(); - Point from(v0.x(), v0.y()); - auto v1 = *n.edge->vertex1(); - Point to(v1.x(), v1.y()); - svg.draw(Line(from, to), "gray", width); - - Point center = from + to; - center *= .5; - //svg.draw_text(center, (std::to_string(std::round(n.edge_length/3e5)/100.)).c_str(), "gray"); - } - } -} - -void draw(SVG &svg, const VoronoiGraph::Nodes& path, coord_t width, const char* color, bool finish=false) { - const VoronoiGraph::Node *prev_node = (finish)? path.back() : nullptr; - int index = 0; - for (auto &node : path) { - ++index; - if (prev_node == nullptr) { - prev_node = node; - continue; - } - Point from(prev_node->vertex->x(), prev_node->vertex->y()); - Point to(node->vertex->x(), node->vertex->y()); - svg.draw(Line(from, to), color, width); - - svg.draw_text(from, std::to_string(index - 1).c_str(), color); - svg.draw_text(to, std::to_string(index).c_str(), color); - prev_node = node; - } -} - -void draw(SVG &svg, const VoronoiGraph::ExPath &path, coord_t width) { - const char *circlePathColor = "green"; - const char *sideBranchesColor = "blue"; - const char *mainPathColor = "red"; - - for (auto &circle : path.circles) { - draw(svg, circle.path, width, circlePathColor, true); - Point center(0, 0); - for (auto p : circle.path) { - center.x() += p->vertex->x(); - center.y() += p->vertex->y(); - } - center.x() /= circle.path.size(); - center.y() /= circle.path.size(); - - svg.draw_text(center, ("C"+std::to_string(&circle - &path.circles.front())).c_str(), circlePathColor); - } - - for (const auto &branches : path.side_branches) { - auto tmp = branches.second; // copy - while (!tmp.empty()){ - const auto &branch = tmp.top(); - auto path = branch.path; - path.insert(path.begin(), branches.first); - draw(svg, path, width, sideBranchesColor); - tmp.pop(); - } - } - - draw(svg, path.path, width, mainPathColor); -} - -std::vector sample_in_center(const ExPolygon & island, - const SampleConfig &config, bool visualize = true) -{ - VD vd; - Lines lines = to_lines(island); - construct_voronoi(lines.begin(), lines.end(), &vd); - Slic3r::Voronoi::annotate_inside_outside(vd, lines); - VoronoiGraph skeleton = getSkeleton(vd, lines); - VoronoiGraph::ExPath longest_path; - std::vector samples = sample_voronoi_graph(skeleton, config, longest_path); - - if(visualize){ // visualization - static int counter = 0; - BoundingBox bb; - double scale = bb.size().x(); - for (const Point &pt : island.contour.points) bb.merge(pt); - SVG svg("voronoi-skeleton-" + std::to_string(++counter) + ".svg", bb); - svg.draw(island, "blue", 0.5f); - draw(svg, skeleton, scale/300); - for (auto l : lines) svg.draw(l, "black", coord_t(scale / 200)); - for (auto p : samples) - svg.draw(p, "lightgreen", coord_t(config.start_distance)); - draw(svg, longest_path, scale / 200); - } - // dump_voronoi_to_svg("tt", vd, Points(), lines); - return samples; -} - -std::vector test_island_sampling(const ExPolygon & island, - const SampleConfig &config) -{ - auto points = sample_in_center(island, config); - CHECK(!points.empty()); - - // all points must be inside of island - for (const auto &point : points) { - CHECK(island.contains(point)); - } - return points; -} - -TEST_CASE("Sample speed test on FrogLegs", "[VoronoiSkeleton]") { - TriangleMesh mesh = load_model("frog_legs.obj"); - TriangleMeshSlicer slicer{&mesh}; - std::vector grid({0.1f}); - std::vector slices; - slicer.slice(grid, SlicingMode::Regular, 0.05f, &slices, [] {}); - ExPolygon frog_leg = slices.front()[1]; - - double size = 3e7; - SampleConfig cfg; - cfg.max_distance = size + 0.1; - cfg.sample_size = size / 5; - cfg.start_distance = 0.2 * size; // radius of support head - cfg.curve_sample = 0.1 * size; - cfg.max_length_for_one_support_point = 3 * size; - - for (int i = 0; i < 100; ++i) { - auto points = sample_in_center(frog_leg, cfg, false); - } -} - -TEST_CASE("Move vector", "[VoronoiSkeleton]") { - struct T{ - std::vector data; - std::map connectors; - }; - - T cp; - cp.data = {2, 8, 16}; - cp.connectors[&cp.data[0]] = &cp.data[2]; // 2 -> 16 - cp.connectors[&cp.data[2]] = &cp.data[0]; // 16 -> 2 - - std::cout << "test data "; - for (auto &i : cp.data) std::cout << i << ", "; - std::cout << "\n"; - for (auto &i : cp.connectors) - std::cout << *(i.first) << " -> " << *(i.second) << "\n"; - std::cout << "end "; - - { - T t; - t.data = {1, 12, 33, 4, 105, 116}; - t.connectors[&t.data[1]] = &t.data[3]; // 12 -> 4 - t.connectors[&t.data[3]] = &t.data[1]; // 12 -> 4 - - cp.data.insert(cp.data.end(), std::move_iterator(t.data.begin()), - std::move_iterator(t.data.end())); - cp.connectors.insert(std::move_iterator(t.connectors.begin()), - std::move_iterator(t.connectors.end())); - } - - std::cout << "test data "; - for (auto &i : cp.data) std::cout << i << ", "; - std::cout << "\n"; - for (auto &i : cp.connectors) - std::cout << *i.first << " -> " << *i.second << "\n"; - std::cout << "end "; -} - -TEST_CASE("Sample small islands", "[VoronoiSkeleton]") -{ - double size = 3e7; - ExPolygon triangle( - Polygon{{.0, .0}, - {size, .0}, - {size / 2., sqrt(size * size - size * size / 4)}}); - ExPolygon sharp_triangle( - Polygon{{.0, size / 2}, {.0, .0}, {2 * size, .0}}); - ExPolygon triangle_with_hole({{.0, .0}, - {size, .0}, - {size / 2., - sqrt(size * size - size * size / 4)}}, - {{size / 4, size / 4}, - {size / 2, size / 2}, - {size / 2, size / 4}}); - ExPolygon square(Polygon{{.0, size}, {.0, .0}, {size, .0}, {size, size}}); - ExPolygon rect( - Polygon{{.0, size}, {.0, .0}, {2 * size, .0}, {2 * size, size}}); - ExPolygon rect_with_hole({{-size, size}, // rect CounterClockWise - {-size, -size}, - {size, -size}, - {size, size}}, - {{0., size / 2}, // inside rect ClockWise - {size / 2, 0.}, - {0., -size / 2}, - {-size / 2, 0.}}); - // need post reorganization of longest path - ExPolygon mountains({{0., 0.}, - {size, 0.}, - {5 * size / 6, size}, - {4 * size / 6, size / 6}, - {3 * size / 7, 2 * size}, - {2 * size / 7, size / 6}, - {size / 7, size}}); - ExPolygon rect_with_4_hole(Polygon{{0., size}, // rect CounterClockWise - {0., 0.}, - {size, 0.}, - {size, size}}); - // inside rects ClockWise - double size5 = size / 5.; - rect_with_4_hole.holes = Polygons{{{size5, 4 * size5}, - {2 * size5, 4 * size5}, - {2 * size5, 3 * size5}, - {size5, 3 * size5}}, - {{3 * size5, 4 * size5}, - {4 * size5, 4 * size5}, - {4 * size5, 3 * size5}, - {3 * size5, 3 * size5}}, - {{size5, 2 * size5}, - {2 * size5, 2 * size5}, - {2 * size5, size5}, - {size5, size5}}, - {{3 * size5, 2 * size5}, - {4 * size5, 2 * size5}, - {4 * size5, size5}, - {3 * size5, size5}}}; - - size_t count_cirlce_lines = 1000; // test stack overfrow - double r_CCW = size / 2; - double r_CW = r_CCW - size / 6; - // CCW: couter clock wise, CW: clock wise - Points circle_CCW, circle_CW; - circle_CCW.reserve(count_cirlce_lines); - circle_CW.reserve(count_cirlce_lines); - for (size_t i = 0; i < count_cirlce_lines; ++i) { - double alpha = (2 * M_PI * i) / count_cirlce_lines; - double sina = sin(alpha); - double cosa = cos(alpha); - circle_CCW.emplace_back(-r_CCW * sina, r_CCW * cosa); - circle_CW.emplace_back(r_CW * sina, r_CW * cosa); - } - ExPolygon double_circle(circle_CCW, circle_CW); - - TriangleMesh mesh = load_model("frog_legs.obj"); - TriangleMeshSlicer slicer{&mesh}; - std::vector grid({ 0.1f }); - std::vector slices; - slicer.slice(grid, SlicingMode::Regular, 0.05f, &slices, [] {}); - ExPolygon frog_leg = slices.front()[1]; // - - SampleConfig cfg; - cfg.max_distance = size + 0.1; - cfg.sample_size = size / 5; - cfg.start_distance = 0.2*size; // radius of support head - cfg.curve_sample = 0.1 *size; - cfg.max_length_for_one_support_point = 3 * size; - - ExPolygons islands = { - triangle - , square - , sharp_triangle - , rect - , rect_with_hole - , triangle_with_hole - , rect_with_4_hole - , mountains - , double_circle - //, frog_leg - }; - for (auto &island : islands) { - auto points = test_island_sampling(island, cfg); - double angle = 3.14 / 3; // cca 60 degree - - island.rotate(angle); - auto pointsR = test_island_sampling(island, cfg); - for (Point &p : pointsR) p.rotate(-angle); - - // points should be equal to pointsR - } - -} -// */ diff --git a/tests/sla_print/sla_supptgen_tests.cpp b/tests/sla_print/sla_supptgen_tests.cpp index 178617c352..15bff2a97a 100644 --- a/tests/sla_print/sla_supptgen_tests.cpp +++ b/tests/sla_print/sla_supptgen_tests.cpp @@ -246,6 +246,31 @@ std::vector test_island_sampling(const ExPolygon & island, return points; } + +TEST_CASE("Sampling speed test on FrogLegs", "[VoronoiSkeleton]") +{ + TriangleMesh mesh = load_model("frog_legs.obj"); + TriangleMeshSlicer slicer{&mesh}; + std::vector grid({0.1f}); + std::vector slices; + slicer.slice(grid, SlicingMode::Regular, 0.05f, &slices, [] {}); + ExPolygon frog_leg = slices.front()[1]; + + double size = 3e7; + SampleConfig cfg; + cfg.max_distance = size + 0.1; + cfg.sample_size = size / 5; + cfg.start_distance = 0.2 * size; // radius of support head + cfg.curve_sample = 0.1 * size; + cfg.max_length_for_one_support_point = 3 * size; + + for (int i = 0; i < 100; ++i) { + auto points = SupportPointGenerator::uniform_cover_island( + frog_leg, cfg); + } +} + + TEST_CASE("Small islands should be supported in center", "[SupGen][VoronoiSkeleton]") { double size = 3e7;