WIP Arc interpolation bugfixes

This commit is contained in:
Vojtech Bubnik 2023-07-15 12:31:55 +02:00
parent 7551b4ffd3
commit 9fe36fc300
4 changed files with 275 additions and 139 deletions

View File

@ -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<double>().norm() :
Geometry::ArcWelder::arc_length(prev_point.cast<float>(), point.cast<float>(), it->radius);
prev_point = point;
}
}
for (const SmoothPathElement &el : path)
l += Geometry::ArcWelder::path_length<double>(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<double>().norm() :
Geometry::ArcWelder::arc_length(prev_point.cast<float>(), point.cast<float>(), 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<double>(*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());

View File

@ -184,41 +184,11 @@ static std::optional<Circle> 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<Arc> 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<Arc> 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<Arc>(out);
return std::fabs(arc_length_difference_relative) >= path_tolerance_percent ?
std::make_optional<Arc>() :
std::make_optional<Arc>(Arc{
*begin,
*std::prev(end),
circle.center,
circle.radius,
arc_dir > 0 ? Orientation::CCW : Orientation::CW
});
}
std::optional<Arc> Arc::try_create_arc(
static inline std::optional<Arc> 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> 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> arc;
while (end != src.end()) {
auto next_end = std::next(end);
if (std::optional<Arc> this_arc = ArcWelder::Arc::try_create_arc(
if (std::optional<Arc> 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>();
double lsqr = v.squaredNorm();
if (lsqr > sqr(distance)) {
path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast<coord_t>(), 0.f, Orientation::CCW });
path.push_back({ last.point + (v * (distance / sqrt(lsqr))).cast<coord_t>() });
// Length to go is zero.
return 0;
}
distance -= sqrt(lsqr);
} else {
// Circular segment
float angle = arc_angle(path.back().point.cast<float>(), last.point.cast<float>(), last.radius);
double len = std::abs(last.radius) * angle;
double angle = arc_angle(path.back().point.cast<double>(), last.point.cast<double>(), 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<float>(), last.point.cast<float>(), last.radius, last.ccw()).cast<coord_t>()),
if (last.ccw())
angle *= -1.;
path.push_back({
last.point.rotated(angle * (distance / len),
arc_center(path.back().point.cast<double>(), last.point.cast<double>(), double(last.radius), last.ccw()).cast<coord_t>()),
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<float>(), it->point.cast<float>(), it->radius, it->ccw()).cast<int64_t>();
Vec2i64 center = arc_center(prev.cast<double>(), it->point.cast<double>(), double(it->radius), it->ccw()).cast<int64_t>();
// Test whether point is inside the wedge.
Vec2i64 v1 = prev.cast<int64_t>() - center;
Vec2i64 v2 = it->point.cast<int64_t>() - center;
Vec2i64 vp = point.cast<int64_t>() - 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<coord_t>() + (vp.cast<double>() * (r / rtest)).cast<coord_t>();
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<coord_t>();
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<double>().norm(); d2 < out.distance2) {
if (double d2 = (path.back().point - point).cast<double>().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<Path, Path> split_at(const Path &path, PathSegmentProjection proj, const double min_segment_length)
std::pair<Path, Path> 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<Path, Path> 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<int64_t>().squaredNorm(); d < sqr(min_segment_length)) {
int split_segment_id = proj.segment_id;
if (int64_t d2 = (proj.point - start.point).cast<int64_t>().squaredNorm(); d2 < sqr(min_segment_length)) {
split_segment = false;
} else if (int64_t d = (proj.point - end.point).cast<int64_t>().squaredNorm(); d < sqr(min_segment_length)) {
++ proj.segment_id;
int64_t d22 = (proj.point - end.point).cast<int64_t>().squaredNorm();
if (d22 < d2)
// Split at the end of the segment.
++ split_segment_id;
} else if (int64_t d2 = (proj.point - end.point).cast<int64_t>().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<int64_t>();
auto vend = (end.point - proj.center).cast<int64_t>();
auto vproj = (proj.point - proj.center).cast<int64_t>();
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;
}

View File

@ -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<typename Derived, typename Derived2>
inline Eigen::Matrix<typename Derived::Scalar, 2, 1, Eigen::DontAlign> arc_center(
template<typename Derived, typename Derived2, typename Float>
inline Eigen::Matrix<Float, 2, 1, Eigen::DontAlign> arc_center(
const Eigen::MatrixBase<Derived> &start_pos,
const Eigen::MatrixBase<Derived2> &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<typename Derived::Scalar, typename Derived2::Scalar>::value, "arc_center(): Both vectors must be of the same type.");
static_assert(std::is_same<typename Derived::Scalar, Float>::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<Float, 2, 1, Eigen::DontAlign>;
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<typename Derived, typename Derived2, typename Derived3>
inline bool inside_arc_wedge_vectors(
const Eigen::MatrixBase<Derived> &start_vec,
const Eigen::MatrixBase<Derived2> &end_vec,
const bool shorter_arc,
const bool ccw,
const Eigen::MatrixBase<Derived3> &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<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::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<typename Derived, typename Derived2, typename Derived3, typename Derived4>
inline bool inside_arc_wedge(
const Eigen::MatrixBase<Derived> &start_pt,
const Eigen::MatrixBase<Derived2> &end_pt,
const Eigen::MatrixBase<Derived3> &center_pt,
const bool shorter_arc,
const bool ccw,
const Eigen::MatrixBase<Derived4> &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<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived4::Scalar>::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<typename Derived, typename Derived2, typename Derived3, typename Float>
inline bool inside_arc_wedge(
const Eigen::MatrixBase<Derived> &start_pt,
const Eigen::MatrixBase<Derived2> &end_pt,
const Float radius,
const bool ccw,
const Eigen::MatrixBase<Derived3> &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<typename Derived::Scalar, typename Derived2::Scalar>::value &&
std::is_same<typename Derived::Scalar, typename Derived3::Scalar>::value &&
std::is_same<typename Derived::Scalar, Float>::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<Segment>;
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<typename FloatType>
inline FloatType segment_length(const Segment &start, const Segment &end)
{
return end.linear() ?
(end.point - start.point).cast<double>().norm() :
arc_length(start.point.cast<float>(), end.point.cast<float>(), end.radius);
(end.point - start.point).cast<FloatType>().norm() :
arc_length(start.point.cast<FloatType>(), end.point.cast<FloatType>(), FloatType(end.radius));
}
template<typename FloatType>
inline FloatType path_length(const Path &path)
{
FloatType len = 0;
for (size_t i = 1; i < path.size(); ++ i)
len += segment_length<FloatType>(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<size_t>::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<double>::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<double>::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<Path, Path> split_at(const Path &path, PathSegmentProjection proj, const double min_segment_length);
std::pair<Path, Path> 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<Path, Path> split_at(const Path &path, const Point &point, const double min_segment_length);

View File

@ -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<double>(), p2.cast<double>(), double(r), ccw);
REQUIRE(is_approx(c, center.cast<double>()));
REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside);
REQUIRE(ArcWelder::inside_arc_wedge(p1.cast<double>(), p2.cast<double>(), double(r), ccw, ptest.cast<double>()) == 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);
}
}
}