mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-12 03:29:00 +08:00
WIP Arc interpolation bugfixes
This commit is contained in:
parent
7551b4ffd3
commit
9fe36fc300
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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> ¢er_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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user