|
@@ -249,6 +249,163 @@ std::vector<Point> MultiPoint::_douglas_peucker(const std::vector<Point>& pts, c
|
|
|
return result_pts;
|
|
|
}
|
|
|
|
|
|
+/// <summary>
|
|
|
+/// douglas_peucker will keep only points that are more than 'tolerance' out of the current polygon.
|
|
|
+/// But when we want to ensure we don't have a segment less than min_length, it's not very usable.
|
|
|
+/// This one is more effective: it will keep all points like the douglas_peucker, and also all points
|
|
|
+/// in-between that satisfies the min_length, ordered by their tolerance.
|
|
|
+/// Note: to have a all 360 points of a circle, then you need 'tolerance <= min_length * (1-cos(1°)) ~= min_length * 0.000155'
|
|
|
+/// Note: douglas_peucker is bad for simplifying circles, as it will create uneven segments.
|
|
|
+/// </summary>
|
|
|
+/// <param name="pts"></param>
|
|
|
+/// <param name="tolerance"></param>
|
|
|
+/// <param name="min_length"></param>
|
|
|
+/// <returns></returns>
|
|
|
+std::vector<Point> MultiPoint::_douglas_peucker_plus(const std::vector<Point>& pts, const double tolerance, const double min_length)
|
|
|
+{
|
|
|
+ std::vector<Point> result_pts;
|
|
|
+ std::vector<size_t> result_idx;
|
|
|
+ double tolerance_sq = tolerance * tolerance;
|
|
|
+ if (!pts.empty()) {
|
|
|
+ const Point* anchor = &pts.front();
|
|
|
+ size_t anchor_idx = 0;
|
|
|
+ const Point* floater = &pts.back();
|
|
|
+ size_t floater_idx = pts.size() - 1;
|
|
|
+ result_pts.reserve(pts.size());
|
|
|
+ result_pts.emplace_back(*anchor);
|
|
|
+ result_idx.reserve(pts.size());
|
|
|
+ result_idx.emplace_back(anchor_idx);
|
|
|
+ if (anchor_idx != floater_idx) {
|
|
|
+ assert(pts.size() > 1);
|
|
|
+ std::vector<size_t> dpStack;
|
|
|
+ dpStack.reserve(pts.size());
|
|
|
+ dpStack.emplace_back(floater_idx);
|
|
|
+ for (;;) {
|
|
|
+ double max_dist_sq = 0.0;
|
|
|
+ size_t furthest_idx = anchor_idx;
|
|
|
+ // find point furthest from line seg created by (anchor, floater) and note it
|
|
|
+ for (size_t i = anchor_idx + 1; i < floater_idx; ++i) {
|
|
|
+ double dist_sq = Line::distance_to_squared(pts[i], *anchor, *floater);
|
|
|
+ if (dist_sq > max_dist_sq) {
|
|
|
+ max_dist_sq = dist_sq;
|
|
|
+ furthest_idx = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // remove point if less than tolerance
|
|
|
+ if (max_dist_sq <= tolerance_sq) {
|
|
|
+ result_pts.emplace_back(*floater);
|
|
|
+ result_idx.emplace_back(floater_idx);
|
|
|
+ anchor_idx = floater_idx;
|
|
|
+ anchor = floater;
|
|
|
+ assert(dpStack.back() == floater_idx);
|
|
|
+ dpStack.pop_back();
|
|
|
+ if (dpStack.empty())
|
|
|
+ break;
|
|
|
+ floater_idx = dpStack.back();
|
|
|
+ } else {
|
|
|
+ floater_idx = furthest_idx;
|
|
|
+ dpStack.emplace_back(floater_idx);
|
|
|
+ }
|
|
|
+ floater = &pts[floater_idx];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ assert(result_pts.front() == pts.front());
|
|
|
+ assert(result_pts.back() == pts.back());
|
|
|
+
|
|
|
+ //TODO use linked list if needed.
|
|
|
+ // add other points that are at not less than min_length dist of the other points.
|
|
|
+ std::vector<double> distances;
|
|
|
+ for (size_t segment_idx = 0; segment_idx < result_idx.size()-1; segment_idx++) {
|
|
|
+ distances.clear();
|
|
|
+ size_t start_idx = result_idx[segment_idx];
|
|
|
+ size_t end_idx = result_idx[segment_idx + 1];
|
|
|
+ if (end_idx - start_idx == 1) continue;
|
|
|
+ //create the list of distances
|
|
|
+ double sum = 0;
|
|
|
+ for (size_t i = start_idx; i < end_idx; i++) {
|
|
|
+ double dist = pts[i].distance_to(pts[i + 1]);
|
|
|
+ distances.push_back(dist);
|
|
|
+ sum += dist;
|
|
|
+ }
|
|
|
+ if (sum < min_length * 2) continue;
|
|
|
+ //if there are too many points and dist, then choose a more difficult sections of ~min_length * 2-4, where we will at least one
|
|
|
+ if (sum > min_length * 4) {
|
|
|
+ //check what is the last index possible
|
|
|
+ double current_sum = 0;
|
|
|
+ size_t last_possible_idx = end_idx;
|
|
|
+ while (current_sum < min_length * 2) {
|
|
|
+ last_possible_idx--;
|
|
|
+ current_sum += distances[last_possible_idx - start_idx];
|
|
|
+ }
|
|
|
+
|
|
|
+ //find the new end point
|
|
|
+ current_sum = 0;
|
|
|
+ size_t current_idx = start_idx;
|
|
|
+ while (current_sum < min_length * 4 && current_idx < last_possible_idx){
|
|
|
+ current_sum += distances[current_idx - start_idx];
|
|
|
+ current_idx ++;
|
|
|
+ }
|
|
|
+
|
|
|
+ // last check, to see if the points are well distributed enough.
|
|
|
+ if (current_sum > min_length * 2 && current_idx > start_idx + 1) {
|
|
|
+ //set new end
|
|
|
+ sum = current_sum;
|
|
|
+ end_idx = current_idx;
|
|
|
+ result_idx.insert(result_idx.begin() + segment_idx + 1, end_idx);
|
|
|
+ result_pts.insert(result_pts.begin() + segment_idx + 1, pts[end_idx]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Point* start_point = &result_pts[segment_idx];
|
|
|
+ Point* end_point = &result_pts[segment_idx + 1];
|
|
|
+
|
|
|
+ //use at least a point, even if it's not in the middle and sum ~= min_length * 2
|
|
|
+ double max_dist_sq = 0.0;
|
|
|
+ size_t furthest_idx = start_idx + 1;
|
|
|
+ // find point furthest from line seg created by (anchor, floater) and note it
|
|
|
+ for (size_t i = start_idx + 1; i < end_idx; ++i) {
|
|
|
+ double dist_sq = Line::distance_to_squared(pts[i], *start_point, *end_point);
|
|
|
+ if (dist_sq > max_dist_sq) {
|
|
|
+ max_dist_sq = dist_sq;
|
|
|
+ furthest_idx = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //add this point and skip it
|
|
|
+ result_idx.insert(result_idx.begin() + segment_idx + 1, furthest_idx);
|
|
|
+ result_pts.insert(result_pts.begin() + segment_idx + 1, pts[furthest_idx]);
|
|
|
+ segment_idx++;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+#if 0
|
|
|
+ {
|
|
|
+ static int iRun = 0;
|
|
|
+ BoundingBox bbox(pts);
|
|
|
+ BoundingBox bbox2(result_pts);
|
|
|
+ bbox.merge(bbox2);
|
|
|
+ //SVG svg(debug_out_path("douglas_peucker_%d.svg", iRun ++).c_str(), bbox);
|
|
|
+
|
|
|
+ std::stringstream stri;
|
|
|
+ stri << "douglas_peucker_" << (iRun++) << ".svg";
|
|
|
+ SVG svg(stri.str());
|
|
|
+ if (pts.front() == pts.back())
|
|
|
+ svg.draw(Polygon(pts), "black");
|
|
|
+ else
|
|
|
+ svg.draw(Polyline(pts), "black");
|
|
|
+ if (result_pts.front() == result_pts.back())
|
|
|
+ svg.draw(Polygon(result_pts), "green");
|
|
|
+ else
|
|
|
+ svg.draw(Polyline(result_pts), "green", scale_(0.1));
|
|
|
+ svg.Close();
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ return result_pts;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
// Visivalingam simplification algorithm https://github.com/slic3r/Slic3r/pull/3825
|
|
|
// thanks to @fuchstraumer
|
|
|
/*
|