diff --git a/src/libslic3r/GCode/SmoothPath.cpp b/src/libslic3r/GCode/SmoothPath.cpp index 4eedd59011..7f4ee21785 100644 --- a/src/libslic3r/GCode/SmoothPath.cpp +++ b/src/libslic3r/GCode/SmoothPath.cpp @@ -9,36 +9,19 @@ namespace Slic3r::GCode { double length(const SmoothPath &path) { double l = 0; - for (const SmoothPathElement &el : path) { - auto it = el.path.begin(); - auto end = el.path.end(); - Point prev_point = it->point; - for (++ it; it != end; ++ it) { - Point point = it->point; - l += it->linear() ? - (point - prev_point).cast().norm() : - Geometry::ArcWelder::arc_length(prev_point.cast(), point.cast(), it->radius); - prev_point = point; - } - } + for (const SmoothPathElement &el : path) + l += Geometry::ArcWelder::path_length(el.path); return l; } // Returns true if the smooth path is longer than a threshold. bool longer_than(const SmoothPath &path, double length) { - for (const SmoothPathElement& el : path) { - auto it = el.path.begin(); - auto end = el.path.end(); - Point prev_point = it->point; - for (++it; it != end; ++it) { - Point point = it->point; - length -= it->linear() ? - (point - prev_point).cast().norm() : - Geometry::ArcWelder::arc_length(prev_point.cast(), point.cast(), it->radius); + for (const SmoothPathElement &el : path) { + for (auto it = std::next(el.path.begin()); it != el.path.end(); ++ it) { + length -= Geometry::ArcWelder::segment_length(*std::prev(it), *it); if (length < 0) return true; - prev_point = point; } } return length < 0; @@ -127,10 +110,14 @@ double clip_end(SmoothPath &path, double distance) if (p.empty()) { path.pop_back(); } else { + // Trailing path was trimmed and it is valid. + assert(path.back().path.size() > 1); assert(distance == 0); + // Distance to go is zero. return 0; } } + // Return distance to go after the whole smooth path was trimmed to zero. return distance; } @@ -221,12 +208,15 @@ SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam( SmoothPath out = this->resolve_or_fit(loop.paths, reverse, resolution); assert(! out.empty()); if (! out.empty()) { + // Find a closest point on a vector of smooth paths. Geometry::ArcWelder::PathSegmentProjection proj; int proj_path = -1; for (const SmoothPathElement &el : out) if (Geometry::ArcWelder::PathSegmentProjection this_proj = Geometry::ArcWelder::point_to_path_projection(el.path, seam_point, proj.distance2); - this_proj.distance2 < proj.distance2) { + this_proj.valid()) { // Found a better (closer) projection. + assert(this_proj.distance2 < proj.distance2); + assert(this_proj.segment_id >= 0 && this_proj.segment_id < el.path.size()); proj = this_proj; proj_path = &el - out.data(); } @@ -237,7 +227,7 @@ SmoothPath SmoothPathCache::resolve_or_fit_split_with_seam( path, proj, seam_point_merge_distance_threshold); if (split.second.empty()) { std::rotate(out.begin(), out.begin() + proj_path + 1, out.end()); - out.back().path = std::move(split.first); + assert(out.back().path == split.first); } else { ExtrusionAttributes attr = out[proj_path].path_attributes; std::rotate(out.begin(), out.begin() + proj_path, out.end()); diff --git a/src/libslic3r/Geometry/ArcWelder.cpp b/src/libslic3r/Geometry/ArcWelder.cpp index 41c3b5f249..177c549b6d 100644 --- a/src/libslic3r/Geometry/ArcWelder.cpp +++ b/src/libslic3r/Geometry/ArcWelder.cpp @@ -184,41 +184,11 @@ static std::optional try_create_circle(const Points::const_iterator begi // ported from ArcWelderLib/ArcWelder/segmented/shape.h class "arc" class Arc { public: - - Arc() {} -#if 0 - Arc(Point center, double radius, Point start, Point end, Orientation dir) : - center(center), - radius(radius), - start_point(start), - end_point(end), - direction(dir) { - if (radius == 0.0 || - start_point == center || - end_point == center || - start_point == end_point) { - is_arc = false; - return; - } - is_arc = true; - } -#endif - - Point center; - double radius { 0 }; - bool is_arc { false }; Point start_point{ 0, 0 }; Point end_point{ 0, 0 }; + Point center; + double radius { 0 }; Orientation direction { Orientation::Unknown }; - - static std::optional try_create_arc( - const Points::const_iterator begin, - const Points::const_iterator end, - double max_radius = default_scaled_max_radius, - double tolerance = default_scaled_resolution, - double path_tolerance_percent = default_arc_length_percent_tolerance); - - bool is_valid() const { return is_arc; } }; static inline int sign(const int64_t i) @@ -274,25 +244,24 @@ static inline std::optional try_create_arc_impl( const double approximate_length = length(begin, end); assert(approximate_length > 0); const double arc_length_difference_relative = (arc_length - approximate_length) / approximate_length; - if (std::fabs(arc_length_difference_relative) >= path_tolerance_percent) - return {}; - Arc out; - out.is_arc = true; - out.direction = arc_dir > 0 ? Orientation::CCW : Orientation::CW; - out.center = circle.center; - out.radius = circle.radius; - out.start_point = *begin; - out.end_point = *std::prev(end); - return std::make_optional(out); + return std::fabs(arc_length_difference_relative) >= path_tolerance_percent ? + std::make_optional() : + std::make_optional(Arc{ + *begin, + *std::prev(end), + circle.center, + circle.radius, + arc_dir > 0 ? Orientation::CCW : Orientation::CW + }); } -std::optional Arc::try_create_arc( +static inline std::optional try_create_arc( const Points::const_iterator begin, const Points::const_iterator end, - double max_radius, - double tolerance, - double path_tolerance_percent) + double max_radius = default_scaled_max_radius, + double tolerance = default_scaled_resolution, + double path_tolerance_percent = default_arc_length_percent_tolerance) { std::optional circle = try_create_circle(begin, end, max_radius, tolerance); if (! circle) @@ -354,7 +323,7 @@ Path fit_path(const Points &src, double tolerance, double fit_circle_percent_tol std::optional arc; while (end != src.end()) { auto next_end = std::next(end); - if (std::optional this_arc = ArcWelder::Arc::try_create_arc( + if (std::optional this_arc = try_create_arc( begin, next_end, ArcWelder::default_scaled_max_radius, tolerance, fit_circle_percent_tolerance); @@ -432,20 +401,24 @@ double clip_end(Path &path, double distance) Vec2d v = (path.back().point - last.point).cast(); double lsqr = v.squaredNorm(); if (lsqr > sqr(distance)) { - path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast(), 0.f, Orientation::CCW }); + path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast() }); + // Length to go is zero. return 0; } distance -= sqrt(lsqr); } else { // Circular segment - float angle = arc_angle(path.back().point.cast(), last.point.cast(), last.radius); - double len = std::abs(last.radius) * angle; + double angle = arc_angle(path.back().point.cast(), last.point.cast(), last.radius); + double len = std::abs(last.radius) * angle; if (len > distance) { // Rotate the segment end point in reverse towards the start point. - path.push_back({ - last.point.rotated(- angle * (distance / len), - arc_center(path.back().point.cast(), last.point.cast(), last.radius, last.ccw()).cast()), + if (last.ccw()) + angle *= -1.; + path.push_back({ + last.point.rotated(angle * (distance / len), + arc_center(path.back().point.cast(), last.point.cast(), double(last.radius), last.ccw()).cast()), last.radius, last.orientation }); + // Length to go is zero. return 0; } distance -= len; @@ -460,11 +433,13 @@ double clip_end(Path &path, double distance) PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2) { assert(path.size() != 1); + // initialized to "invalid" state. PathSegmentProjection out; out.distance2 = search_radius2; if (path.size() < 2 || path.front().point == point) { // First point is the closest point. if (path.empty()) { + // No closest point available. } else if (const Point p0 = path.front().point; p0 == point) { out.segment_id = 0; out.point = p0; @@ -475,12 +450,16 @@ PathSegmentProjection point_to_path_projection(const Path &path, const Point &po out.distance2 = d2; } } else { + assert(path.size() >= 2); + // min_point_it will contain an end point of a segment with a closest projection found + // or path.cbegin() if no such closest projection closer than search_radius2 was found. auto min_point_it = path.cbegin(); Point prev = path.front().point; - for (auto it = path.cbegin() + 1; it != path.cend(); ++ it) { + for (auto it = std::next(path.cbegin()); it != path.cend(); ++ it) { if (it->linear()) { // Linear segment Point proj; + // distance_to_squared() will possibly return the start or end point of a line segment. if (double d2 = line_alg::distance_to_squared(Line(prev, it->point), point, &proj); d2 < out.distance2) { out.point = proj; out.distance2 = d2; @@ -488,24 +467,25 @@ PathSegmentProjection point_to_path_projection(const Path &path, const Point &po } } else { // Circular arc - Vec2i64 center = arc_center(prev.cast(), it->point.cast(), it->radius, it->ccw()).cast(); + Vec2i64 center = arc_center(prev.cast(), it->point.cast(), double(it->radius), it->ccw()).cast(); // Test whether point is inside the wedge. Vec2i64 v1 = prev.cast() - center; Vec2i64 v2 = it->point.cast() - center; Vec2i64 vp = point.cast() - center; - bool inside = it->radius > 0 ? - // Smaller (convex) wedge. - (it->ccw() ? - cross2(v1, vp) > 0 && cross2(vp, v2) > 0 : - cross2(v1, vp) < 0 && cross2(vp, v2) < 0) : - // Larger (concave) wedge. - (it->ccw() ? - cross2(v2, vp) < 0 || cross2(vp, v1) < 0 : - cross2(v2, vp) > 0 || cross2(vp, v1) > 0); - if (inside) { + if (inside_arc_wedge_vectors(v1, v2, it->radius > 0, it->ccw(), vp)) { // Distance of the radii. - if (double d2 = sqr(std::abs(it->radius) - sqrt(double(v1.squaredNorm()))); d2 < out.distance2) { + const auto r = double(std::abs(it->radius)); + const auto rtest = sqrt(double(vp.squaredNorm())); + if (double d2 = sqr(rtest - r); d2 < out.distance2) { + if (rtest > SCALED_EPSILON) + // Project vp to the arc. + out.point = center.cast() + (vp.cast() * (r / rtest)).cast(); + else + // Test point is very close to the center of the radius. Any point of the arc is the closest. + // Pick the start. + out.point = prev; out.distance2 = d2; + out.center = center.cast(); min_point_it = it; } } else { @@ -521,44 +501,89 @@ PathSegmentProjection point_to_path_projection(const Path &path, const Point &po } if (! path.back().linear()) { // Calculate distance to the end point. - if (double d2 = (path.back().point - point).cast().norm(); d2 < out.distance2) { + if (double d2 = (path.back().point - point).cast().squaredNorm(); d2 < out.distance2) { out.point = path.back().point; out.distance2 = d2; min_point_it = std::prev(path.end()); } } - out.segment_id = min_point_it - path.begin(); + // If a new closes point was found, it is closer than search_radius2. + assert((min_point_it == path.cbegin()) == (out.distance2 == search_radius2)); + // Output is not valid yet. + assert(! out.valid()); + if (min_point_it != path.cbegin()) { + // Make it valid by setting the segment. + out.segment_id = std::prev(min_point_it) - path.begin(); + assert(out.valid()); + } } + assert(! out.valid() || (out.segment_id >= 0 && out.segment_id < path.size())); return out; } -std::pair split_at(const Path &path, PathSegmentProjection proj, const double min_segment_length) +std::pair split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length) { + assert(proj.valid()); + assert(! proj.valid() || (proj.segment_id >= 0 && proj.segment_id < path.size())); std::pair out; - if (proj.segment_id == 0 && proj.point == path.front().point) - out.second = path; - else if (proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point)) + if (! proj.valid() || proj.segment_id + 1 == path.size() || (proj.segment_id + 2 == path.size() && proj.point == path.back().point)) out.first = path; + else if (proj.segment_id == 0 && proj.point == path.front().point) + out.second = path; else { + // Path will likely be split to two pieces. + assert(proj.valid() && proj.segment_id >= 0 && proj.segment_id + 1 < path.size()); const Segment &start = path[proj.segment_id]; const Segment &end = path[proj.segment_id + 1]; bool split_segment = true; - if (int64_t d = (proj.point - start.point).cast().squaredNorm(); d < sqr(min_segment_length)) { + int split_segment_id = proj.segment_id; + if (int64_t d2 = (proj.point - start.point).cast().squaredNorm(); d2 < sqr(min_segment_length)) { split_segment = false; - } else if (int64_t d = (proj.point - end.point).cast().squaredNorm(); d < sqr(min_segment_length)) { - ++ proj.segment_id; + int64_t d22 = (proj.point - end.point).cast().squaredNorm(); + if (d22 < d2) + // Split at the end of the segment. + ++ split_segment_id; + } else if (int64_t d2 = (proj.point - end.point).cast().squaredNorm(); d2 < sqr(min_segment_length)) { + ++ split_segment_id; split_segment = false; } if (split_segment) { - out.first.assign(path.begin(), path.begin() + proj.segment_id + 2); - out.second.assign(path.begin() + proj.segment_id, path.end()); + out.first.assign(path.begin(), path.begin() + split_segment_id + 2); + out.second.assign(path.begin() + split_segment_id, path.end()); + assert(out.first[out.first.size() - 2] == start); + assert(out.first.back() == end); + assert(out.second.front() == start); + assert(out.second[1] == end); + assert(out.first.size() + out.second.size() == path.size() + 2); + assert(out.first.back().radius == out.second[1].radius); out.first.back().point = proj.point; out.second.front().point = proj.point; + if (end.radius < 0) { + // A large arc (> PI) was split. + // At least one of the two arches that were created by splitting the original arch will become smaller. + // Make the radii of those arches that became < PI positive. + // In case of a projection onto an arc, proj.center should be filled in and valid. + auto vstart = (start.point - proj.center).cast(); + auto vend = (end.point - proj.center).cast(); + auto vproj = (proj.point - proj.center).cast(); + if (bool first_arc_minor = (cross2(vstart, vproj) > 0) == end.ccw()) + // Make the radius of a minor arc positive. + out.first.back().radius *= -1.f; + if (bool second_arc_minor = (cross2(vproj, vend) > 0) == end.ccw()) + // Make the radius of a minor arc positive. + out.second[1].radius *= -1.f; + } } else { - out.first.assign(path.begin(), path.begin() + proj.segment_id + 1); - out.second.assign(path.begin() + proj.segment_id, path.end()); + // Split at the start of proj.segment_id. + out.first.assign(path.begin(), path.begin() + split_segment_id + 1); + out.second.assign(path.begin() + split_segment_id, path.end()); + assert(out.first.size() + out.second.size() == path.size() + 1); + assert(out.first.back() == (split_segment_id == proj.segment_id ? start : end)); + assert(out.second.front() == (split_segment_id == proj.segment_id ? start : end)); } + assert(out.first.size() > 1); + assert(out.second.size() > 1); out.second.front().radius = 0; } diff --git a/src/libslic3r/Geometry/ArcWelder.hpp b/src/libslic3r/Geometry/ArcWelder.hpp index 86e4eb31a9..003e6baa0c 100644 --- a/src/libslic3r/Geometry/ArcWelder.hpp +++ b/src/libslic3r/Geometry/ArcWelder.hpp @@ -1,30 +1,3 @@ -// The following code for merging circles into arches originates from https://github.com/FormerLurker/ArcWelderLib - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Arc Welder: Anti-Stutter Library -// -// Compresses many G0/G1 commands into G2/G3(arc) commands where possible, ensuring the tool paths stay within the specified resolution. -// This reduces file size and the number of gcodes per second. -// -// Uses the 'Gcode Processor Library' for gcode parsing, position processing, logging, and other various functionality. -// -// Copyright(C) 2021 - Brad Hochgesang -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// This program is free software : you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the -// GNU Affero General Public License for more details. -// -// -// You can contact the author at the following email address: -// FormerLurker@pm.me -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - #ifndef slic3r_Geometry_ArcWelder_hpp_ #define slic3r_Geometry_ArcWelder_hpp_ @@ -38,18 +11,18 @@ namespace Slic3r { namespace Geometry { namespace ArcWelder { // positive radius: take shorter arc // negative radius: take longer arc // radius must NOT be zero! -template -inline Eigen::Matrix arc_center( +template +inline Eigen::Matrix arc_center( const Eigen::MatrixBase &start_pos, const Eigen::MatrixBase &end_pos, - const typename Derived::Scalar radius, + const typename Float radius, const bool is_ccw) { static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "arc_center(): first parameter is not a 2D vector"); static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "arc_center(): second parameter is not a 2D vector"); static_assert(std::is_same::value, "arc_center(): Both vectors must be of the same type."); + static_assert(std::is_same::value, "arc_center(): Radius must be of the same type as the vectors."); assert(radius != 0); - using Float = typename Derived::Scalar; using Vector = Eigen::Matrix; auto v = end_pos - start_pos; Float q2 = v.squaredNorm(); @@ -100,6 +73,69 @@ inline typename Derived::Scalar arc_length( return arc_angle(start_pos, end_pos, radius) * std::abs(radius); } +// Test whether a point is inside a wedge of an arc. +template +inline bool inside_arc_wedge_vectors( + const Eigen::MatrixBase &start_vec, + const Eigen::MatrixBase &end_vec, + const bool shorter_arc, + const bool ccw, + const Eigen::MatrixBase &query_vec) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): start_vec is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): end_vec is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge_vectors(): query_vec is not a 2D vector"); + static_assert(std::is_same::value && + std::is_same::value, "inside_arc_wedge_vectors(): All vectors must be of the same type."); + return shorter_arc ? + // Smaller (convex) wedge. + (ccw ? + cross2(start_vec, query_vec) > 0 && cross2(query_vec, end_vec) > 0 : + cross2(start_vec, query_vec) < 0 && cross2(query_vec, end_vec) < 0) : + // Larger (concave) wedge. + (ccw ? + cross2(end_vec, query_vec) < 0 || cross2(query_vec, start_vec) < 0 : + cross2(end_vec, query_vec) > 0 || cross2(query_vec, start_vec) > 0); +} + +template +inline bool inside_arc_wedge( + const Eigen::MatrixBase &start_pt, + const Eigen::MatrixBase &end_pt, + const Eigen::MatrixBase ¢er_pt, + const bool shorter_arc, + const bool ccw, + const Eigen::MatrixBase &query_pt) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): center_pt is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived4::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector"); + static_assert(std::is_same::value && + std::is_same::value && + std::is_same::value, "inside_arc_wedge(): All vectors must be of the same type."); + return inside_arc_wedge_vectors(start_pt - center_pt, end_pt - center_pt, shorter_arc, ccw, query_pt - center_pt); +} + +template +inline bool inside_arc_wedge( + const Eigen::MatrixBase &start_pt, + const Eigen::MatrixBase &end_pt, + const Float radius, + const bool ccw, + const Eigen::MatrixBase &query_pt) +{ + static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "inside_arc_wedge(): start_pt is not a 2D vector"); + static_assert(Derived2::IsVectorAtCompileTime && int(Derived2::SizeAtCompileTime) == 2, "inside_arc_wedge(): end_pt is not a 2D vector"); + static_assert(Derived3::IsVectorAtCompileTime && int(Derived3::SizeAtCompileTime) == 2, "inside_arc_wedge(): query_pt is not a 2D vector"); + static_assert(std::is_same::value && + std::is_same::value && + std::is_same::value, "inside_arc_wedge(): All vectors + radius must be of the same type."); + return inside_arc_wedge(start_pt, end_pt, + arc_center(start_pt, end_pt, radius, ccw), + radius > 0, ccw, query_pt); +} + // Discretize arc given the radius, orientation and maximum deviation from the arc. // Returned polygon starts with p1, ends with p2 and it is discretized to guarantee the maximum deviation. Points arc_discretize(const Point &p1, const Point &p2, const double radius, const bool ccw, const double deviation); @@ -133,20 +169,39 @@ struct Segment bool cw() const { return orientation == Orientation::CW; } }; +inline bool operator==(const Segment &lhs, const Segment &rhs) { + return lhs.point == rhs.point && lhs.radius == rhs.radius && lhs.orientation == rhs.orientation; +} + using Segments = std::vector; using Path = Segments; // Interpolate polyline path with a sequence of linear / circular segments given the interpolation tolerance. // Only convert to polyline if zero tolerance. // Convert to polyline and decimate polyline if zero fit_circle_percent_tolerance. +// Path fitting is inspired with the arc fitting algorithm in +// Arc Welder: Anti-Stutter Library by Brad Hochgesang FormerLurker@pm.me +// https://github.com/FormerLurker/ArcWelderLib Path fit_path(const Points &points, double tolerance, double fit_circle_percent_tolerance); + +// Decimate polyline into a smooth path structure using Douglas-Peucker polyline decimation algorithm. inline Path fit_polyline(const Points &points, double tolerance) { return fit_path(points, tolerance, 0.); } -inline double segment_length(const Segment &start, const Segment &end) +template +inline FloatType segment_length(const Segment &start, const Segment &end) { return end.linear() ? - (end.point - start.point).cast().norm() : - arc_length(start.point.cast(), end.point.cast(), end.radius); + (end.point - start.point).cast().norm() : + arc_length(start.point.cast(), end.point.cast(), FloatType(end.radius)); +} + +template +inline FloatType path_length(const Path &path) +{ + FloatType len = 0; + for (size_t i = 1; i < path.size(); ++ i) + len += segment_length(path[i - 1], path[i]); + return len; } // Estimate minimum path length of a segment cheaply without having to calculate center of an arc and it arc length. @@ -160,7 +215,7 @@ inline int64_t estimate_min_segment_length(const Segment &start, const Segment & } else { // Arc with angle > PI. // Returns estimate of PI * r - return - 3 * int64_t(end.radius); + return - int64_t(3) * int64_t(end.radius); } } @@ -185,7 +240,10 @@ struct PathSegmentProjection { // Start segment of a projection on the path. size_t segment_id { std::numeric_limits::max() }; + // Projection of the point on the segment. Point point { 0, 0 }; + // If the point lies on an arc, the arc center is cached here. + Point center { 0, 0 }; // Square of a distance of the projection. double distance2 { std::numeric_limits::max() }; @@ -194,7 +252,7 @@ struct PathSegmentProjection // Returns closest segment and a parameter along the closest segment of a path to a point. PathSegmentProjection point_to_path_projection(const Path &path, const Point &point, double search_radius2 = std::numeric_limits::max()); // Split a path into two paths at a segment point. Snap to an existing point if the projection of "point is closer than min_segment_length. -std::pair split_at(const Path &path, PathSegmentProjection proj, const double min_segment_length); +std::pair split_at(const Path &path, const PathSegmentProjection &proj, const double min_segment_length); // Split a path into two paths at a point closest to "point". Snap to an existing point if the projection of "point is closer than min_segment_length. std::pair split_at(const Path &path, const Point &point, const double min_segment_length); diff --git a/tests/libslic3r/test_arc_welder.cpp b/tests/libslic3r/test_arc_welder.cpp index 6c4b58e00f..3cc13d1bae 100644 --- a/tests/libslic3r/test_arc_welder.cpp +++ b/tests/libslic3r/test_arc_welder.cpp @@ -199,3 +199,66 @@ TEST_CASE("arc fitting", "[ArcWelder]") { } } } + +TEST_CASE("arc wedge test", "[ArcWelder]") { + using namespace Slic3r::Geometry; + + WHEN("test point inside wedge, arc from { 2, 1 } to { 1, 2 }") { + const int64_t s = 1000000; + const Vec2i64 p1{ 2 * s, s }; + const Vec2i64 p2{ s, 2 * s }; + const Vec2i64 center{ s, s }; + const int64_t radius{ s }; + auto test = [center]( + // Arc data + const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw, + // Test data + const Vec2i64 &ptest, const bool ptest_inside) { + const Vec2d c = ArcWelder::arc_center(p1.cast(), p2.cast(), double(r), ccw); + REQUIRE(is_approx(c, center.cast())); + REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside); + REQUIRE(ArcWelder::inside_arc_wedge(p1.cast(), p2.cast(), double(r), ccw, ptest.cast()) == ptest_inside); + }; + auto test_quadrants = [center, test]( + // Arc data + const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw, + // Test data + const Vec2i64 &ptest1, const bool ptest_inside1, + const Vec2i64 &ptest2, const bool ptest_inside2, + const Vec2i64 &ptest3, const bool ptest_inside3, + const Vec2i64 &ptest4, const bool ptest_inside4) { + test(p1, p2, r, ccw, ptest1 + center, ptest_inside1); + test(p1, p2, r, ccw, ptest2 + center, ptest_inside2); + test(p1, p2, r, ccw, ptest3 + center, ptest_inside3); + test(p1, p2, r, ccw, ptest4 + center, ptest_inside4); + }; + THEN("90 degrees arc, CCW") { + test_quadrants(p1, p2, radius, true, + Vec2i64{ s, s }, true, + Vec2i64{ s, - s }, false, + Vec2i64{ - s, s }, false, + Vec2i64{ - s, - s }, false); + } + THEN("270 degrees arc, CCW") { + test_quadrants(p2, p1, -radius, true, + Vec2i64{ s, s }, false, + Vec2i64{ s, - s }, true, + Vec2i64{ - s, s }, true, + Vec2i64{ - s, - s }, true); + } + THEN("90 degrees arc, CW") { + test_quadrants(p2, p1, radius, false, + Vec2i64{ s, s }, true, + Vec2i64{ s, - s }, false, + Vec2i64{ - s, s }, false, + Vec2i64{ - s, - s }, false); + } + THEN("270 degrees arc, CW") { + test_quadrants(p1, p2, -radius, false, + Vec2i64{ s, s }, false, + Vec2i64{ s, - s }, true, + Vec2i64{ - s, s }, true, + Vec2i64{ - s, - s }, true); + } + } +}