diff --git a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp index 2a15e6cb47..dc27d6c6a4 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp +++ b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp @@ -1087,14 +1087,15 @@ using Neighbor = VoronoiGraph::Node::Neighbor; /// struct ThinPart { - // Transition from thick to thin part (one of the ends) - // NOTE: When start.ratio <= 0 than island do not contain thick part - Position start; + // Ceneter of longest path inside island part + // longest path is choosen from: + // shortest connection path between ends + // OR farest path to node from end + // OR farest path between nodes(only when ends are empty) + Position center; // Transition from tiny to thick part (without start position) Positions ends; - - bool is_only_thin_part() const { return ends.empty() && start.ratio <= 0.f; } }; using ThinParts = std::vector; @@ -1114,14 +1115,25 @@ struct ThickPart }; using ThickParts = std::vector; +void create_thin_supports(const ThinParts &parts, SupportIslandPoints &results, + const Lines &lines, const SampleConfig &config) { + assert(!parts.empty()); + /////////////////////// continue here + +} + // Search for interfaces // 1. thin to min_wide // 2. min_wide to max_center // 3. max_center to Thick enum class IslandPartType { thin, middle, thick }; +// transition into neighbor part struct IslandPartChange { - Position position; + // position on the way out of island part + // Position::Neighbor::node is target(twin neighbor has source) + // Position::ration define position on connection between nodes + Position position; size_t part_index; }; using IslandPartChanges = std::vector; @@ -1137,7 +1149,8 @@ struct IslandPart { IslandPartChanges changes; // sum of all lengths inside of part - // IMPROVE: better will be length of longest path + // IMPROVE1: Separate calculation localy into function merge_middle_parts_into_biggest_neighbor + // IMPROVE2: better will be length of longest path // Used as rule to connect(merge) middle part of island to its biggest neighbour // NOTE: No solution for island with 2 biggest neighbors with same sum_lengths. coord_t sum_lengths = 0; @@ -1351,9 +1364,9 @@ void merge_middle_parts_into_biggest_neighbor(IslandParts& island_parts) { void merge_same_neighbor_type_parts(IslandParts &island_parts) { // connect neighbor parts with same type for (size_t island_part_index = 0; island_part_index < island_parts.size(); ++island_part_index) { - assert(island_part.type != IslandPartType::middle); // only thin or thick parts while (true) { const IslandPart &island_part = island_parts[island_part_index]; + assert(island_part.type != IslandPartType::middle); // only thin or thick parts const IslandPartChanges &changes = island_part.changes; auto change_it = std::find_if(changes.begin(), changes.end(), [&island_parts, type = island_part.type](const IslandPartChange &change) { @@ -1364,8 +1377,327 @@ void merge_same_neighbor_type_parts(IslandParts &island_parts) { } } +/// +/// Find shortest distances between changes (combination of changes) +/// and choose the longest distance or farest node distance from changes +/// +/// transition into different part island +/// [optional]Center of longest path +/// Length of island part defined as longest distance on graph inside part +coord_t get_longest_distance(const IslandPartChanges& changes, Position* center = nullptr) { + const Neighbor *front_twin = VoronoiGraphUtils::get_twin(*changes.front().position.neighbor); + if (changes.size() == 2 && front_twin == changes.back().position.neighbor) { + // Special case when part lay only on one neighbor + if (center != nullptr){ + *center = changes.front().position;// copy + center->ratio = (center->ratio + changes.back().position.ratio)/2; + } + return static_cast(changes.front().position.neighbor->length() * + (1 - changes.front().position.ratio - changes.back().position.ratio)); + } + + struct ShortestDistance{ + coord_t distance; + size_t prev_node_distance_index; + }; + using ShortestDistances = std::vector; + // for each island part node find distance to changes + struct NodeDistance { + // island part node + const VoronoiGraph::Node *node; + // shortest distance to node from change + ShortestDistances shortest_distances; // size == changes.size() + }; + using NodeDistances = std::vector; + NodeDistances node_distances; + + const coord_t no_distance = std::numeric_limits::max(); + const size_t no_index = std::numeric_limits::max(); + size_t count = changes.size(); + for (const IslandPartChange &change : changes) { + const VoronoiGraph::Node *node = VoronoiGraphUtils::get_twin(*change.position.neighbor)->node; + ShortestDistances shortest_distances(count, ShortestDistance{no_distance, no_index}); + shortest_distances[&change - &changes.front()].distance = change.position.calc_distance(); + node_distances.push_back(NodeDistance{node, std::move(shortest_distances)}); + } + + // use sorted changes for faster check of neighbors + IslandPartChanges sorted_changes = changes; // copy + std::sort(sorted_changes.begin(), sorted_changes.end(), + [](const IslandPartChange &a, const IslandPartChange &b) { + return a.position.neighbor < b.position.neighbor; + }); + auto exist_part_change_for_neighbor = [&sorted_changes](const Neighbor *neighbor) { + auto it = std::lower_bound(sorted_changes.begin(), sorted_changes.end(), neighbor, + [](const IslandPartChange &change, const Neighbor *neighbor_) { + return change.position.neighbor < neighbor_; }); + if (it == sorted_changes.end()) return false; + return it->position.neighbor == neighbor; + }; + + // Queue of island nodes to propagate shortest distance into their neigbors + // contain indices into node_distances + std::vector process; + for (size_t i = 1; i < count; i++) process.push_back(i); // zero index is start + size_t next_distance_index = 0; // zero index is start + const Neighbor *prev_neighbor = front_twin; + // propagate distances into neighbors + while (true /* next_distance_index < node_distances.size()*/) { + const NodeDistance &node_distance = node_distances[next_distance_index]; + next_distance_index = -1; // set to no value ... index > node_distances.size() + for (const Neighbor &neighbor : node_distance.node->neighbors) { + if (&neighbor == prev_neighbor) continue; + if (exist_part_change_for_neighbor(&neighbor)) + continue; // change is search graph limit + + // IMPROVE: use binary search + auto node_distance_it = std::find_if(node_distances.begin(), node_distances.end(), + [node = neighbor.node](const NodeDistance& d) { + return d.node == node;} ); + if (node_distance_it == node_distances.end()) { + // create new node distance + ShortestDistances new_shortest_distances = node_distance.shortest_distances; // copy + for (ShortestDistance &d : new_shortest_distances) + if (d.distance != no_distance) { + d.distance += neighbor.length(); + d.prev_node_distance_index = &node_distance - &node_distances.front(); + } + if (next_distance_index < node_distances.size()) + process.push_back(next_distance_index); // store for next processing + next_distance_index = node_distances.size(); + prev_neighbor = VoronoiGraphUtils::get_twin(neighbor); + node_distances.push_back(NodeDistance{neighbor.node, new_shortest_distances}); + continue; + } + + bool exist_distance_change = false; + // update distances + for (size_t i = 0; i < count; ++i) { + const ShortestDistance &d = node_distance.shortest_distances[i]; + if (d.distance == no_distance) continue; + coord_t new_distance = d.distance + static_cast(neighbor.length()); + if (ShortestDistance ¤t_distance = node_distance_it->shortest_distances[i]; + current_distance.distance > new_distance) { + current_distance.distance = new_distance; + current_distance.prev_node_distance_index = i; + exist_distance_change = true; + } + } + if (!exist_distance_change) + continue; // no change in distances + + size_t item_index = node_distance_it - node_distances.begin(); + // process store unique indices into node_distances + if(std::find(process.begin(), process.end(), item_index) != process.end()) + continue; // already in process + + if (next_distance_index < node_distances.size()) + process.push_back(next_distance_index); // store for next processing + next_distance_index = item_index; + prev_neighbor = VoronoiGraphUtils::get_twin(neighbor); + } + + if (next_distance_index >= node_distances.size()){ + if (process.empty()) + break; // no more nodes to process + next_distance_index = process.back(); + process.pop_back(); + prev_neighbor = nullptr; // do not know previous neighbor + continue; + } + } + + // find farest distance node from changes + coord_t farest_from_change = 0; + size_t change_index = 0; + NodeDistance &farest_distnace = node_distances.front(); + for (const NodeDistance &node_distance : node_distances) + for (const ShortestDistance& d : node_distance.shortest_distances) + if (farest_from_change < d.distance) { + farest_from_change = d.distance; + change_index = &d - &node_distance.shortest_distances.front(); + farest_distnace = node_distance; + } + + // farest distance between changes + // till node distances do not change order than index of change is index of closest node of change + size_t source_change = count; + for (size_t i = 0; i < (count-1); ++i) { + const NodeDistance &node_distance = node_distances[i]; + const ShortestDistance &distance_to_change = node_distance.shortest_distances[i]; + for (size_t j = i+1; j < count; ++j) { + coord_t distance = node_distance.shortest_distances[j].distance + distance_to_change.distance; + if (farest_from_change < distance) { + // this change is farest from other changes + farest_from_change = distance; + change_index = j; + source_change = i; + farest_distnace = node_distance; + } + } + } + + // center is not needed so return only farest distance + if (center == nullptr) + return farest_from_change; + + // Next lines are for calculation of center for longest path + coord_t half_distance = farest_from_change / 2; + + // check if center is on change neighbor + auto is_ceneter_on_change_neighbor = [&changes, center, half_distance](size_t change_index) { + const Position &position = changes[change_index].position; + if (position.calc_distance() < half_distance) + return false; + // center lay on neighbour with change + center->neighbor = position.neighbor; + center->ratio = position.ratio - half_distance / position.neighbor->length(); + return true; + }; + if (is_ceneter_on_change_neighbor(source_change) || + is_ceneter_on_change_neighbor(change_index)) + return farest_from_change; + + NodeDistance *prev_node_distance = &farest_distnace; + NodeDistance *node_distance = nullptr; + // iterate over longest path to find center(half distance) + while (prev_node_distance->shortest_distances[change_index].distance > half_distance) { + node_distance = prev_node_distance; + size_t prev_index = node_distance->shortest_distances[change_index].prev_node_distance_index; + // case with center on change neighbor is already handled, so prev_index should be valid + assert(prev_index != no_index); + prev_node_distance = &node_distances[prev_index]; + } + + // case with center on change neighbor should be already handled + assert(node_distance != nullptr); + assert(node_distance->shortest_distances[change_index].distance >= half_distance); + assert(prev_node_distance->shortest_distances[change_index].distance <= half_distance); + coord_t to_half_distance = half_distance - node_distance->shortest_distances[change_index].distance; + // find neighbor between node_distance and prev_node_distance + for (const Neighbor &n : node_distance->node->neighbors) { + if (n.node != prev_node_distance->node) + continue; + center->neighbor = &n; + center->ratio = to_half_distance / n.length(); + return farest_from_change; + } + + // weird situation when center is not found + assert(false); + return farest_from_change; +} + +/// +/// Remove island part with index +/// Merge all neighbors of deleted part together and create merged part on lowest index of merged parts +/// +/// All existing island parts with type only thin OR thick +/// index of island part to remove +/// modified part index and all removed part indices +std::pair> merge_negihbor(IslandParts &island_parts, size_t index) { + // merge all neighbors into one part + std::vector remove_indices; + const IslandPartChanges &changes = island_parts[index].changes; + // all neighbor should be the same type which is different to current one. + assert(std::find_if(changes.begin(), changes.end(), [&island_parts, type = island_parts[index].type] + (const IslandPartChange &c) { return island_parts[c.part_index].type == type; }) == changes.end()); + remove_indices.reserve(changes.size()); + + // collect changes from neighbors for result part + IslandPartChanges modified_changes; + size_t modified_index = index; // smallest index of merged parts + for (const IslandPartChange &change : changes) { + size_t remove_index = change.part_index; + if (remove_index < modified_index) // remove part with bigger index + std::swap(remove_index, modified_index); + remove_indices.push_back(remove_index); + // iterate neighbor changes and collect only changes to other neighbors + for (const IslandPartChange &n_change : island_parts[change.part_index].changes) { + if (n_change.part_index == index) + continue; // skip back to removed part + if(std::find_if(changes.begin(), changes.end(), [i = n_change.part_index] + (const IslandPartChange &change){ return change.part_index == i;}) == changes.end()) + continue; // skip removed part changes + modified_changes.push_back(n_change); + } + } + + // Set result part after merge + IslandPart& island_part = island_parts[modified_index]; + island_part.type = island_parts[changes.front().part_index].type; // type of neighbor + island_part.changes = modified_changes; + island_part.sum_lengths = 0; // invalid value after merge + + std::sort(remove_indices.begin(), remove_indices.end()); + // remove parts from island parts + for (size_t i = 1; i <= remove_indices.size(); i++) + island_parts.erase(island_parts.begin() + remove_indices[remove_indices.size() - i]); + + // Set neighbors neighbors to point on modified_index + std::vector neighbors_neighbors_indices; + for (const IslandPartChange &change : modified_changes) + neighbors_neighbors_indices.push_back(change.part_index); + std::sort(neighbors_neighbors_indices.begin(), neighbors_neighbors_indices.end()); + neighbors_neighbors_indices.erase(std::unique(neighbors_neighbors_indices.begin(), + neighbors_neighbors_indices.end()), neighbors_neighbors_indices.end()); + for (size_t part_index : neighbors_neighbors_indices) + for (IslandPartChange &change : island_parts[part_index].changes) + if (std::lower_bound(remove_indices.begin(), remove_indices.end(), + change.part_index) != remove_indices.end()) + change.part_index = modified_index; + + return std::make_pair(index, remove_indices); +} + +/// +/// Calculate all distances between changes(combination of changes) +/// Choose the longest distance between change for each island part(part_length). +/// Merge island parts in order from shortest path_length +/// Till path_length is smaller than config::min_part_length +/// +/// Only thin or thick parts +/// Minimal length of part to not be merged into neighbors +void merge_short_parts(IslandParts &island_parts, coord_t min_part_length) { + // should be called only for multiple island parts, at least 2 + assert(island_parts.size() > 1); + if (island_parts.size() <= 1) return; // nothing to merge + + // only thin OR thick parts + assert(std::find_if(island_parts.begin(), island_parts.end(), [](const IslandPart &i) + {return i.type != IslandPartType::thin && i.type != IslandPartType::thick; }) == island_parts.end()); + + // same size as island_parts + std::vector part_lengths; + part_lengths.reserve(island_parts.size()); + for (const IslandPart& island_part: island_parts) + part_lengths.push_back(get_longest_distance(island_part.changes)); + + // Merge island parts in order from shortest length + while(true){ + // find smallest part + size_t smallest_part_index = std::min_element(part_lengths.begin(), part_lengths.end()) - part_lengths.begin(); + if (part_lengths[smallest_part_index] >= min_part_length) + break; // all parts are long enough + + auto [index, remove_indices] = merge_negihbor(island_parts, smallest_part_index); + + // update part lengths + part_lengths[index] = get_longest_distance(island_parts[index].changes); + for (size_t remove_index : remove_indices) // remove lengths for removed parts + part_lengths.erase(part_lengths.begin() + remove_index); + } +} + +ThinPart create_only_thin_part(const VoronoiGraph::ExPath &path) { + std::optional path_center_opt = + SampleIslandUtils::create_position_on_path(path.nodes, path.length / 2); + assert(path_center_opt.has_value()); + return ThinPart{*path_center_opt, /*ends*/ {}}; +} + std::pair convert_island_parts_to_thin_thick( - const IslandParts& island_parts, const Neighbor* start_neighbor) + const IslandParts& island_parts, const Neighbor* start_neighbor, const VoronoiGraph::ExPath &path) { // always must be at least one island part assert(!island_parts.empty()); @@ -1374,7 +1706,7 @@ std::pair convert_island_parts_to_thin_thick( // convert island parts into result if (island_parts.size() == 1) return island_parts.front().type == IslandPartType::thin ? - std::make_pair(ThinParts{ThinPart{Position{start_neighbor, -1.f}, /*ends*/ {}}}, ThickParts{}) : + std::make_pair(ThinParts{create_only_thin_part(path)}, ThickParts{}) : std::make_pair(ThinParts{}, ThickParts{ThickPart{Position{start_neighbor, -1.f}, /*ends*/ {}}}); std::pair result; @@ -1383,9 +1715,10 @@ std::pair convert_island_parts_to_thin_thick( for (const IslandPart& i:island_parts) { if (i.type == IslandPartType::thin) { ThinPart thin_part; - thin_part.start = i.changes.front().position; + // discard distance, only center is needed + get_longest_distance(i.changes, &thin_part.center); thin_parts.reserve(i.changes.size() - 1); - std::transform(i.changes.begin()+1, i.changes.end(), std::back_inserter(thin_part.ends), + std::transform(i.changes.begin() + 1, i.changes.end(), std::back_inserter(thin_part.ends), [](const IslandPartChange &change) { return change.position; }); thin_parts.push_back(thin_part); } else { @@ -1406,7 +1739,8 @@ std::pair convert_island_parts_to_thin_thick( /// /// Longest path over island /// Island border -/// Define border between thin and thick part +/// Define border between thin and thick part +/// and minimal length of separable part /// Thin and thick parts std::pair separate_thin_thick( const VoronoiGraph::ExPath &path, const Lines &lines, const SampleConfig &config @@ -1414,7 +1748,6 @@ std::pair separate_thin_thick( // Check input assert(!path.nodes.empty()); assert(lines.size() >= 3); // at least triangle - assert(SampleConfigFactory::verify(config)); // Start dividing on some border of island const VoronoiGraph::Node *start_node = path.nodes.front(); @@ -1449,7 +1782,7 @@ std::pair separate_thin_thick( if (auto process_it = std::find_if(process.begin(), process.end(), is_oposit_item); process_it != process.end()) { // solve loop back - next_item.node = nullptr; + next_item.node = nullptr; // do not use item as next one merge_parts_and_fix_process(island_parts, item, process_it->i, next_item.i, process); // branch is already processed process.erase(process_it); @@ -1469,8 +1802,9 @@ std::pair separate_thin_thick( merge_middle_parts_into_biggest_neighbor(island_parts); merge_same_neighbor_type_parts(island_parts); + merge_short_parts(island_parts, config.min_part_length); - return convert_island_parts_to_thin_thick(island_parts, start_neighbor); + return convert_island_parts_to_thin_thick(island_parts, start_neighbor, path); } } // namespace @@ -1514,7 +1848,11 @@ SupportIslandPoints SampleIslandUtils::sample_expath( // TODO: 3) Triangle of points // eval outline and find three point create almost equilateral triangle + // 4) Thin and thick support + SupportIslandPoints result; auto [thin, thick] = separate_thin_thick(path, lines, config); + if (!thin.empty()) create_thin_supports(thin, result, lines, config); + //if (!thick.empty()) create_thick_supports(thick, lines, config); // IMPROVE: Erase continous sampling: Extract ExPath and than sample uniformly whole ExPath CenterStarts center_starts;