From 10c05ca01e2119fd49fc4e21c9b2f3d75ec0c456 Mon Sep 17 00:00:00 2001 From: Filip Sykala Date: Fri, 30 Apr 2021 10:07:44 +0200 Subject: [PATCH] Align outline points --- .../SLA/SupportIslands/LineUtils.cpp | 13 +- .../SLA/SupportIslands/LineUtils.hpp | 9 + .../SLA/SupportIslands/SampleIslandUtils.cpp | 229 ++++++++++++++---- .../SLA/SupportIslands/SampleIslandUtils.hpp | 13 + .../SLA/SupportIslands/SupportIslandPoint.cpp | 92 ++++++- .../SLA/SupportIslands/SupportIslandPoint.hpp | 160 ++++++++++-- 6 files changed, 443 insertions(+), 73 deletions(-) diff --git a/src/libslic3r/SLA/SupportIslands/LineUtils.cpp b/src/libslic3r/SLA/SupportIslands/LineUtils.cpp index b518910a92..9fe3e9ead6 100644 --- a/src/libslic3r/SLA/SupportIslands/LineUtils.cpp +++ b/src/libslic3r/SLA/SupportIslands/LineUtils.cpp @@ -279,7 +279,18 @@ std::optional LineUtils::intersection(const Line &ray1, const Lin return (ray1.a.cast() + t * v1); } -LineUtils::LineConnection LineUtils::create_line_connection(const Slic3r::Lines &lines) +double LineUtils::foot(const Line &line, const Point &point) +{ + Vec2d a = line.a.cast(); + Vec2d vec = point.cast() - a; + Vec2d b = line.b.cast(); + Vec2d dir = b - a; + double l2 = dir.squaredNorm(); + return vec.dot(dir) / l2; +} + +LineUtils::LineConnection LineUtils::create_line_connection( + const Slic3r::Lines &lines) { LineConnection line_connection; static const size_t bad_index = -1; diff --git a/src/libslic3r/SLA/SupportIslands/LineUtils.hpp b/src/libslic3r/SLA/SupportIslands/LineUtils.hpp index 3befc9e33b..cd29843528 100644 --- a/src/libslic3r/SLA/SupportIslands/LineUtils.hpp +++ b/src/libslic3r/SLA/SupportIslands/LineUtils.hpp @@ -119,6 +119,15 @@ public: /// line direction static Point direction(const Line &line) { return line.b - line.a; } + /// + /// Calculate foot point in maner of Geometry::foot_pt + /// - without unnecessary conversion + /// + /// input line + /// point to search foot on line + /// ration betwen point line.a and line.b (in range from 0. to 1.) + static double foot(const Line &line, const Point& point); + // line index, using LineConnection = std::map>; /// diff --git a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp index 34b4d659bf..acfead6bc6 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp +++ b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp @@ -1209,69 +1209,204 @@ SampleIslandUtils::Field SampleIslandUtils::create_field( return field; } +std::pair> +SampleIslandUtils::outline_offset(const Slic3r::ExPolygon &island, + coord_t offset_distance) +{ + Polygons polygons = offset(island, -offset_distance, ClipperLib::jtSquare); + if (polygons.empty()) return {}; // no place for support point + assert(polygons.front().is_counter_clockwise()); + ExPolygon offseted(polygons.front()); + for (size_t i = 1; i < polygons.size(); ++i) { + Polygon &hole = polygons[i]; + assert(hole.is_clockwise()); + offseted.holes.push_back(hole); + } + + // TODO: Connect indexes for convert during creation of offset + // !! this implementation was fast for develop BUT NOT for running !! + Lines island_lines = to_lines(island); + Lines offset_lines = to_lines(offseted); + // Convert index map from island index to offseted index + std::map converter; + for (size_t island_line_index = 0; island_line_index < island_lines.size(); ++island_line_index) { + const Line &island_line = island_lines[island_line_index]; + Vec2f dir1 = LineUtils::direction(island_line).cast(); + for (size_t offset_line_index = 0; offset_line_index < offset_lines.size(); ++offset_line_index) { + const Line &offset_line = offset_lines[offset_line_index]; + Vec2f dir2 = LineUtils::direction(offset_line).cast(); + if (fabs(dir1.dot(dir2)) < 1e-4) { // in similar direction + Point middle = offset_line.a / 2 + offset_line.b / 2; + double distance = island_line.perp_distance_to(middle); + if (fabs(distance - offset_distance) < 20) { + // found offseted line + converter[island_line_index] = offset_line_index; + break; + } + } + } + } + + return {offseted, converter}; +} + SupportIslandPoints SampleIslandUtils::sample_outline( const Field &field, const SampleConfig &config) { - // TODO: fix it by neighbor line angles - // Do not create inner expoly and test all points to lay inside - Polygons polygons = offset(field.border,-config.minimal_distance_from_outline + 5, ClipperLib::jtSquare); - if (polygons.empty()) return {}; // no place for support point - ExPolygon inner(polygons.front()); - for (size_t i = 1; i < polygons.size(); ++i) { - Polygon &hole = polygons[i]; - inner.holes.push_back(hole); - } - + const ExPolygon &border = field.border; + const Polygon &contour = border.contour; + assert(field.source_indexes.size() >= contour.size()); + // convert map from field index to inner(line index) + std::map field_2_inner; + ExPolygon inner; + std::tie(inner, field_2_inner) = outline_offset(border, config.minimal_distance_from_outline); + coord_t max_align_distance = config.max_align_distance; coord_t sample_distance = config.outline_sample_distance; - coord_t outline_distance = config.minimal_distance_from_outline; SupportIslandPoints result; - auto add_sample = [&](const Line &line, size_t source_index, coord_t& last_support) { - double line_length_double = line.length(); - coord_t line_length = static_cast(line_length_double); + + using RestrictionPtr = std::shared_ptr; + auto add_sample = [&](size_t index, const RestrictionPtr& restriction, coord_t &last_support) { + using Position = SupportOutlineIslandPoint::Position; + const Line & line = restriction->lines[index]; + const double &line_length_double = restriction->lengths[index]; + coord_t line_length = static_cast(std::round(line_length_double)); if (last_support + line_length > sample_distance) { - Point dir = LineUtils::direction(line); - Point perp = PointUtils::perp(dir); - double size = perp.cast().norm(); - Point move_from_outline = perp * (outline_distance / size); + Point direction = LineUtils::direction(line); do { double ratio = (sample_distance - last_support) / line_length_double; - Point point = line.a + dir * ratio + move_from_outline; - - // TODO: improve prevent sampling out of field near sharp corner - // use range for possible ratio - if (inner.contains(point)) - result.emplace_back(std::make_unique( - point, source_index, SupportIslandPoint::Type::outline)); - + result.emplace_back( + std::make_unique( + Position(index, ratio), restriction, + SupportIslandPoint::Type::outline) + ); last_support -= sample_distance; } while (last_support + line_length > sample_distance); } last_support += line_length; }; - Lines contour_lines = to_lines(field.border.contour); - coord_t last_support = sample_distance / 2; - for (const Line &line : contour_lines) { - size_t index = &line - &contour_lines.front(); - assert(field.source_indexes.size() > index); - size_t source_index = field.source_indexes[index]; - if (source_index == field.source_indexe_for_change) { - last_support = sample_distance / 2; - continue; + auto add_circle_sample = [&](const Polygon& polygon) { + // IMPROVE: find interesting points to start sampling + Lines lines = to_lines(polygon); + std::vector lengths; + lengths.reserve(lines.size()); + double sum_lengths = 0; + for (const Line &line : lines) { + double length = line.length(); + sum_lengths += length; + lengths.push_back(length); } - add_sample(line, source_index, last_support); - } - size_t index_offset = contour_lines.size(); - for (const Polygon &hole : field.border.holes) { - Lines hole_lines = to_lines(hole); - coord_t last_support = sample_distance / 2; - for (const Line &line : hole_lines) { - size_t hole_line_index = (&line - &hole_lines.front()); - size_t index = index_offset + hole_line_index; - assert(field.source_indexes.size() > index); + // no samples on this polygon + + using Restriction = SupportOutlineIslandPoint::RestrictionCircleSequence; + auto restriction = std::make_shared(lines, lengths, max_align_distance); + coord_t last_support = std::min(static_cast(sum_lengths), sample_distance) / 2; + for (size_t index = 0; index < lines.size(); ++index) { + add_sample(index, restriction, last_support); + } + }; + + // sample line sequence + auto add_lines_samples = [&](const Lines &inner_lines, + size_t first_index, + size_t last_index) { + Lines lines; + // is over start ? + if (first_index > last_index) { + size_t count = first_index + inner_lines.size() - last_index; + lines.reserve(count); + std::copy(inner_lines.begin() + last_index, + inner_lines.end(), + std::back_inserter(lines)); + std::copy(inner_lines.begin(), + inner_lines.begin() + first_index, + std::back_inserter(lines)); + } else { + size_t count = last_index - first_index; + lines.reserve(count); + std::copy(inner_lines.begin() + first_index, + inner_lines.begin() + last_index, + std::back_inserter(lines)); + } + + // IMPROVE: find interesting points to start sampling + std::vector lengths; + lengths.reserve(lines.size()); + double sum_lengths = 0; + for (const Line &line : lines) { + double length = line.length(); + sum_lengths += length; + lengths.push_back(length); + } + + using Restriction = SupportOutlineIslandPoint::RestrictionLineSequence; + auto restriction = std::make_shared(lines, lengths, max_align_distance); + + // CHECK: Is correct to has always one support on outline sequence? + // or no sample small sequence at all? + coord_t last_support = std::min(static_cast(sum_lengths), sample_distance) / 2; + for (size_t index = 0; index < lines.size(); ++index) { + add_sample(index, restriction, last_support); + } + }; + + const size_t& change_index = field.source_indexe_for_change; + auto sample_polygon = [&](const Polygon &polygon, + const Polygon &inner_polygon, + size_t index_offset) { + // contain polygon tiny wide change? + size_t first_change_index = polygon.size(); + for (size_t polygon_index = 0; polygon_index < polygon.size(); ++polygon_index) { + size_t index = polygon_index + index_offset; + assert(index < field.source_indexes.size()); size_t source_index = field.source_indexes[index]; - add_sample(line, source_index, last_support); + if (source_index == change_index) { + // found change from wide to tiny part + first_change_index = polygon_index; + break; + } } - index_offset += hole_lines.size(); + + // is polygon without change + if (first_change_index == polygon.size()) { + add_circle_sample(inner_polygon); + } else { // exist change create line sequences + // initialize with non valid values + Lines inner_lines = to_lines(inner_polygon); + size_t inner_invalid = inner_lines.size(); + // first and last index to inner lines + size_t inner_first = inner_invalid; + size_t inner_last = inner_invalid; + for (size_t polygon_index = first_change_index + 1; + polygon_index != first_change_index; ++polygon_index) { + if (polygon_index == polygon.size()) polygon_index = 0; + size_t index = polygon_index + index_offset; + assert(index < field.source_indexes.size()); + size_t source_index = field.source_indexes[index]; + if (source_index == change_index) { + if (inner_first == inner_invalid) continue; + // create Restriction object + add_lines_samples(inner_lines, inner_first, inner_last); + inner_first = inner_invalid; + inner_last = inner_invalid; + continue; + } + auto convert_index_item = field_2_inner.find(polygon_index); + // check if exist inner line + if (convert_index_item == field_2_inner.end()) continue; + inner_last = convert_index_item->second - index_offset; + // initialize first index + if (inner_first == inner_invalid) inner_first = inner_last; + } + } + }; + + size_t index_offset = 0; + sample_polygon(contour, inner.contour, index_offset); + index_offset = contour.size(); + for (size_t hole_index = 0; hole_index < border.holes.size(); ++hole_index) { + const Polygon &hole = border.holes[hole_index]; + sample_polygon(hole, inner.holes[hole_index], index_offset); + index_offset += hole.size(); } return result; } diff --git a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.hpp b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.hpp index 2a62a71786..a20ffcde42 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.hpp +++ b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.hpp @@ -289,8 +289,21 @@ public: /// support for outline static SupportIslandPoints sample_outline(const Field & field, const SampleConfig &config); +private: + /// + /// create offsetted field + /// + /// source field + /// distance from outline + /// offseted field + /// First - offseted island outline + /// Second - map for convert source field index to result border index + /// + static std::pair> + outline_offset(const Slic3r::ExPolygon &island, coord_t offset_distance); // debug draw functions +public : static void draw(SVG & svg, const Field &field, bool draw_border_line_indexes = false, diff --git a/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.cpp b/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.cpp index 36b921bcd3..c46dd4660a 100644 --- a/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.cpp +++ b/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.cpp @@ -1,5 +1,6 @@ #include "SupportIslandPoint.hpp" #include "VoronoiGraphUtils.hpp" +#include "LineUtils.hpp" using namespace Slic3r::sla; @@ -39,9 +40,7 @@ coord_t SupportIslandPoint::move(const Point &destination) { Point diff = destination - point; point = destination; - // TODO: check move out of island !! - // + need island ExPolygon - return diff.x() + diff.y(); // Manhatn distance + return abs(diff.x()) + abs(diff.y()); // Manhatn distance } std::string SupportIslandPoint::to_string(const Type &type) @@ -86,9 +85,88 @@ coord_t SupportCenterIslandPoint::move(const Point &destination) VoronoiGraphUtils::align(position, destination, configuration->max_align_distance); Point new_point = VoronoiGraphUtils::create_edge_point(position); - Point move = new_point - point; - point = new_point; - coord_t manhatn_distance = abs(move.x()) + abs(move.y()); - return manhatn_distance; + return SupportIslandPoint::move(new_point); } +/////////////// +// Point on Outline +/////////////// + +SupportOutlineIslandPoint::SupportOutlineIslandPoint( + Position position, std::shared_ptr restriction, Type type) + : SupportIslandPoint(calc_point(position, *restriction), type) + , position(position) + , restriction(std::move(restriction)) +{} + +bool SupportOutlineIslandPoint::can_move() const { return true; } + +coord_t SupportOutlineIslandPoint::move(const Point &destination) +{ + size_t index = position.index; + MoveResult closest = create_result(index, destination); + + const double &length = restriction->lengths[position.index]; + double distance = (1.0 - position.ratio) * length; + while (distance < restriction->max_align_distance) { + auto next_index = restriction->next_index(index); + if (!next_index.has_value()) break; + index = *next_index; + update_result(closest, index, destination); + distance += restriction->lengths[index]; + } + + index = position.index; + distance = static_cast(position.ratio) * length; + while (distance < restriction->max_align_distance) { + auto prev_index = restriction->prev_index(index); + if (!prev_index.has_value()) break; + index = *prev_index; + update_result(closest, index, destination); + distance += restriction->lengths[index]; + } + + // apply closest result of move + this->point = closest.point; + this->position = closest.position; + return closest.distance; +} + +Slic3r::Point SupportOutlineIslandPoint::calc_point(const Position &position, const Restriction &restriction) +{ + const Line &line = restriction.lines[position.index]; + Point direction = LineUtils::direction(line); + return line.a + direction * position.ratio; +} + +SupportOutlineIslandPoint::MoveResult SupportOutlineIslandPoint::create_result( + size_t index, const Point &destination) +{ + const Line &line = restriction->lines[index]; + double line_ratio_full = LineUtils::foot(line, destination); + double line_ratio = std::clamp(line_ratio_full, 0., 1.); + Position new_position(index, line_ratio); + Point new_point = calc_point(new_position, *restriction); + double point_distance = (new_point - destination).cast().norm(); + return MoveResult(new_position, new_point, point_distance); +} + +void SupportOutlineIslandPoint::update_result(MoveResult & result, + size_t index, + const Point &destination) +{ + const Line &line = restriction->lines[index]; + double line_ratio_full = LineUtils::foot(line, destination); + double line_ratio = std::clamp(line_ratio_full, 0., 1.); + Position new_position(index, line_ratio); + Point new_point = calc_point(new_position, *restriction); + Point diff = new_point - destination; + if (abs(diff.x()) > result.distance) return; + if (abs(diff.y()) > result.distance) return; + double point_distance = diff.cast().norm(); + if (result.distance > point_distance) { + result.distance = point_distance; + result.position = new_position; + result.point = new_point; + } +} \ No newline at end of file diff --git a/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp b/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp index ddad595f0f..59a3d0a2d8 100644 --- a/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp +++ b/src/libslic3r/SLA/SupportIslands/SupportIslandPoint.hpp @@ -107,33 +107,157 @@ public: }; /// -/// DTO Support point laying on Outline of island +/// Support point laying on Outline of island /// Restriction to move only on outline /// class SupportOutlineIslandPoint : public SupportIslandPoint { public: - // index of line form island outline - size_t index; + // definition of restriction + class Restriction; + + struct Position + { + // index of line form island outline - index into Restriction + // adress line inside inner polygon --> SupportOutline + size_t index; + + // define position on line by ratio + // from 0 (line point a) + // to 1 (line point b) + float ratio; + + Position(size_t index, float ratio) : index(index), ratio(ratio) {} + }; + Position position; + + + // store lines for allowed move - with distance from island source lines + std::shared_ptr restriction; public: - SupportOutlineIslandPoint(Slic3r::Point point, - size_t index, - Type type = Type::outline) - : SupportIslandPoint(point, type), index(index) - {} + SupportOutlineIslandPoint(Position position, + std::shared_ptr restriction, + Type type = Type::outline); + // return true + bool can_move() const override; - bool can_move() const override { return true; } + /// + /// Move nearest to destination point + /// only along restriction lines + /// + change current position + /// + /// Wanted support position + /// move distance manhatn + coord_t move(const Point &destination) override; - coord_t move(const Point &destination) override - { - // TODO: For decide of move need information about - // + island outlines \ May be - // + distance from outline / offseted outlines - // + search distance for allowed move over outlines(count, distance) - assert(false); // Not implemented - return 0; - } + /// + /// Calculate 2d point belong to line position + /// + /// Define position on line from restriction + /// Hold lines + /// Position in 2d + static Point calc_point(const Position & position, + const Restriction &restriction); + + /// + /// Keep data for align support point on bordred of island + /// Define possible move of point along outline + /// IMPROVE: Should contain list of Points on outline. + /// (to keep maximal distance of neighbor points on outline) + /// + class Restriction + { + public: + // line restriction + // must be connected line.a == prev_line.b && line.b == next_line.a + Lines lines; + + // keep stored line lengths + // same size as lines + std::vector lengths; + + // maximal distance for search nearest line to destination point during aligning + coord_t max_align_distance; + + Restriction(Lines lines, + std::vector lengths, + coord_t max_align_distance) + : lines(lines) + , lengths(lengths) + , max_align_distance(max_align_distance) + { + assert(lines.size() == lengths.size()); + } + + virtual std::optional next_index(size_t index) const = 0; + virtual std::optional prev_index(size_t index) const = 0; + }; + + class RestrictionLineSequence: public Restriction + { + public: + // inherit constructors + using Restriction::Restriction; + + virtual std::optional next_index(size_t index) const override + { + assert(index < lines.size()); + ++index; + if (index >= lines.size()) return {}; // index out of range + return index; + } + + virtual std::optional prev_index(size_t index) const override + { + assert(index < lines.size()); + if (index >= lines.size()) return {}; // index out of range + if (index == 0) return {}; // no prev line + return index - 1; + } + }; + + class RestrictionCircleSequence : public Restriction + { + public: + // inherit constructors + using Restriction::Restriction; + + virtual std::optional next_index(size_t index) const override + { + assert(index < lines.size()); + if (index >= lines.size()) return {}; // index out of range + ++index; + if (index == lines.size()) return 0; + return index; + } + + virtual std::optional prev_index(size_t index) const override + { + assert(index < lines.size()); + if (index >= lines.size()) return {}; // index out of range + if (index == 0) return lines.size() - 1; + return index - 1; + } + }; + +private: + // DTO for result of move + struct MoveResult + { + // define position on restriction line + Position position; + // point laying on restricted line + Point point; + // distance point on restricted line from destination point + double distance; + + MoveResult(Position position, Point point, double distance) + : position(position), point(point), distance(distance) + {} + }; + MoveResult create_result(size_t index, const Point &destination); + void update_result(MoveResult& result, size_t index, const Point &destination); }; } // namespace Slic3r::sla